Fix 1669 | Refactor display of sizes in tables (#2100)
* Use sector as default display * Display tables in sector size * Refactor size * Update * Update * fix flake8 --------- Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
This commit is contained in:
parent
9e3e4a5df5
commit
b141609990
|
|
@ -14,6 +14,7 @@ from .device_model import (
|
||||||
PartitionTable,
|
PartitionTable,
|
||||||
Unit,
|
Unit,
|
||||||
Size,
|
Size,
|
||||||
|
SectorSize,
|
||||||
SubvolumeModification,
|
SubvolumeModification,
|
||||||
DeviceGeometry,
|
DeviceGeometry,
|
||||||
PartitionType,
|
PartitionType,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ class DiskLayoutConfiguration:
|
||||||
status=ModificationStatus(partition['status']),
|
status=ModificationStatus(partition['status']),
|
||||||
fs_type=FilesystemType(partition['fs_type']),
|
fs_type=FilesystemType(partition['fs_type']),
|
||||||
start=Size.parse_args(partition['start']),
|
start=Size.parse_args(partition['start']),
|
||||||
length=Size.parse_args(partition['length']),
|
length=Size.parse_args(partition['size']),
|
||||||
mount_options=partition['mount_options'],
|
mount_options=partition['mount_options'],
|
||||||
mountpoint=Path(partition['mountpoint']) if partition['mountpoint'] else None,
|
mountpoint=Path(partition['mountpoint']) if partition['mountpoint'] else None,
|
||||||
dev_path=Path(partition['dev_path']) if partition['dev_path'] else None,
|
dev_path=Path(partition['dev_path']) if partition['dev_path'] else None,
|
||||||
|
|
@ -138,80 +138,89 @@ class Unit(Enum):
|
||||||
|
|
||||||
sectors = 'sectors' # size in sector
|
sectors = 'sectors' # size in sector
|
||||||
|
|
||||||
Percent = '%' # size in percentile
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_units() -> List[str]:
|
def get_all_units() -> List[str]:
|
||||||
return [u.name for u in Unit]
|
return [u.name for u in Unit]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_si_units() -> List[Unit]:
|
||||||
|
return [u for u in Unit if 'i' not in u.name and u.name != 'sectors']
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SectorSize:
|
||||||
|
value: int
|
||||||
|
unit: Unit
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
match self.unit:
|
||||||
|
case Unit.sectors:
|
||||||
|
raise ValueError('Unit type sector not allowed for SectorSize')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default() -> SectorSize:
|
||||||
|
return SectorSize(512, Unit.B)
|
||||||
|
|
||||||
|
def json(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
'value': self.value,
|
||||||
|
'unit': self.unit.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_args(cls, arg: Dict[str, Any]) -> SectorSize:
|
||||||
|
return SectorSize(
|
||||||
|
arg['value'],
|
||||||
|
Unit[arg['unit']]
|
||||||
|
)
|
||||||
|
|
||||||
|
def normalize(self) -> int:
|
||||||
|
"""
|
||||||
|
will normalize the value of the unit to Byte
|
||||||
|
"""
|
||||||
|
return int(self.value * self.unit.value) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Size:
|
class Size:
|
||||||
value: int
|
value: int
|
||||||
unit: Unit
|
unit: Unit
|
||||||
sector_size: Optional[Size] = None # only required when unit is sector
|
sector_size: SectorSize
|
||||||
total_size: Optional[Size] = None # required when operating on percentages
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.unit == Unit.sectors and self.sector_size is None:
|
if not isinstance(self.sector_size, SectorSize):
|
||||||
raise ValueError('Sector size is required when unit is sectors')
|
raise ValueError('sector size must be of type SectorSize')
|
||||||
elif self.unit == Unit.Percent:
|
|
||||||
if self.value < 0 or self.value > 100:
|
|
||||||
raise ValueError('Percentage must be between 0 and 100')
|
|
||||||
elif self.total_size is None:
|
|
||||||
raise ValueError('Total size is required when unit is percentage')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _total_size(self) -> Size:
|
|
||||||
"""
|
|
||||||
Save method to get the total size, mainly to satisfy mypy
|
|
||||||
This shouldn't happen as the Size object fails instantiation on missing total size
|
|
||||||
"""
|
|
||||||
if self.unit == Unit.Percent and self.total_size is None:
|
|
||||||
raise ValueError('Percent unit size must specify a total size')
|
|
||||||
return self.total_size # type: ignore
|
|
||||||
|
|
||||||
def json(self) -> Dict[str, Any]:
|
def json(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'value': self.value,
|
'value': self.value,
|
||||||
'unit': self.unit.name,
|
'unit': self.unit.name,
|
||||||
'sector_size': self.sector_size.json() if self.sector_size else None,
|
'sector_size': self.sector_size.json() if self.sector_size else None
|
||||||
'total_size': self._total_size.json() if self._total_size else None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_args(cls, size_arg: Dict[str, Any]) -> Size:
|
def parse_args(cls, size_arg: Dict[str, Any]) -> Size:
|
||||||
sector_size = size_arg['sector_size']
|
sector_size = size_arg['sector_size']
|
||||||
total_size = size_arg['total_size']
|
|
||||||
|
|
||||||
return Size(
|
return Size(
|
||||||
size_arg['value'],
|
size_arg['value'],
|
||||||
Unit[size_arg['unit']],
|
Unit[size_arg['unit']],
|
||||||
Size.parse_args(sector_size) if sector_size else None,
|
SectorSize.parse_args(sector_size),
|
||||||
Size.parse_args(total_size) if total_size else None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def convert(
|
def convert(
|
||||||
self,
|
self,
|
||||||
target_unit: Unit,
|
target_unit: Unit,
|
||||||
sector_size: Optional[Size] = None,
|
sector_size: Optional[SectorSize] = None
|
||||||
total_size: Optional[Size] = None
|
|
||||||
) -> Size:
|
) -> Size:
|
||||||
if target_unit == Unit.sectors and sector_size is None:
|
if target_unit == Unit.sectors and sector_size is None:
|
||||||
raise ValueError('If target has unit sector, a sector size must be provided')
|
raise ValueError('If target has unit sector, a sector size must be provided')
|
||||||
|
|
||||||
# not sure why we would ever wanna convert to percentages
|
|
||||||
if target_unit == Unit.Percent and total_size is None:
|
|
||||||
raise ValueError('Missing parameter total size to be able to convert to percentage')
|
|
||||||
|
|
||||||
if self.unit == target_unit:
|
if self.unit == target_unit:
|
||||||
return self
|
return self
|
||||||
elif self.unit == Unit.Percent:
|
|
||||||
amount = int(self._total_size._normalize() * (self.value / 100))
|
|
||||||
return Size(amount, Unit.B)
|
|
||||||
elif self.unit == Unit.sectors:
|
elif self.unit == Unit.sectors:
|
||||||
norm = self._normalize()
|
norm = self._normalize()
|
||||||
return Size(norm, Unit.B).convert(target_unit, sector_size)
|
return Size(norm, Unit.B, self.sector_size).convert(target_unit, sector_size)
|
||||||
else:
|
else:
|
||||||
if target_unit == Unit.sectors and sector_size is not None:
|
if target_unit == Unit.sectors and sector_size is not None:
|
||||||
norm = self._normalize()
|
norm = self._normalize()
|
||||||
|
|
@ -219,7 +228,7 @@ class Size:
|
||||||
return Size(sectors, Unit.sectors, sector_size)
|
return Size(sectors, Unit.sectors, sector_size)
|
||||||
else:
|
else:
|
||||||
value = int(self._normalize() / target_unit.value) # type: ignore
|
value = int(self._normalize() / target_unit.value) # type: ignore
|
||||||
return Size(value, target_unit)
|
return Size(value, target_unit, self.sector_size)
|
||||||
|
|
||||||
def as_text(self) -> str:
|
def as_text(self) -> str:
|
||||||
return self.format_size(
|
return self.format_size(
|
||||||
|
|
@ -230,31 +239,45 @@ class Size:
|
||||||
def format_size(
|
def format_size(
|
||||||
self,
|
self,
|
||||||
target_unit: Unit,
|
target_unit: Unit,
|
||||||
sector_size: Optional[Size] = None,
|
sector_size: Optional[SectorSize] = None,
|
||||||
include_unit: bool = True
|
include_unit: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
if self.unit == Unit.Percent:
|
target_size = self.convert(target_unit, sector_size)
|
||||||
return f'{self.value}%'
|
|
||||||
else:
|
if include_unit:
|
||||||
target_size = self.convert(target_unit, sector_size)
|
return f'{target_size.value} {target_unit.name}'
|
||||||
if include_unit:
|
return f'{target_size.value}'
|
||||||
return f'{target_size.value} {target_unit.name}'
|
|
||||||
return f'{target_size.value}'
|
def format_highest(self, include_unit: bool = True) -> str:
|
||||||
|
si_units = Unit.get_si_units()
|
||||||
|
all_si_values = [self.convert(si) for si in si_units]
|
||||||
|
filtered = filter(lambda x: x.value >= 1, all_si_values)
|
||||||
|
|
||||||
|
# we have to get the max by the unit value as we're interested
|
||||||
|
# in getting the value in the highest possible unit without floats
|
||||||
|
si_value = max(filtered, key=lambda x: x.unit.value)
|
||||||
|
|
||||||
|
if include_unit:
|
||||||
|
return f'{si_value.value} {si_value.unit.name}'
|
||||||
|
return f'{si_value.value}'
|
||||||
|
|
||||||
def _normalize(self) -> int:
|
def _normalize(self) -> int:
|
||||||
"""
|
"""
|
||||||
will normalize the value of the unit to Byte
|
will normalize the value of the unit to Byte
|
||||||
"""
|
"""
|
||||||
if self.unit == Unit.Percent:
|
if self.unit == Unit.sectors and self.sector_size is not None:
|
||||||
return self.convert(Unit.B).value
|
return self.value * self.sector_size.normalize()
|
||||||
elif self.unit == Unit.sectors and self.sector_size is not None:
|
|
||||||
return self.value * self.sector_size._normalize()
|
|
||||||
return int(self.value * self.unit.value) # type: ignore
|
return int(self.value * self.unit.value) # type: ignore
|
||||||
|
|
||||||
def __sub__(self, other: Size) -> Size:
|
def __sub__(self, other: Size) -> Size:
|
||||||
src_norm = self._normalize()
|
src_norm = self._normalize()
|
||||||
dest_norm = other._normalize()
|
dest_norm = other._normalize()
|
||||||
return Size(abs(src_norm - dest_norm), Unit.B)
|
return Size(abs(src_norm - dest_norm), Unit.B, self.sector_size)
|
||||||
|
|
||||||
|
def __add__(self, other: Size) -> Size:
|
||||||
|
src_norm = self._normalize()
|
||||||
|
dest_norm = other._normalize()
|
||||||
|
return Size(abs(src_norm + dest_norm), Unit.B, self.sector_size)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self._normalize() < other._normalize()
|
return self._normalize() < other._normalize()
|
||||||
|
|
@ -296,14 +319,22 @@ class _PartitionInfo:
|
||||||
mountpoints: List[Path]
|
mountpoints: List[Path]
|
||||||
btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = field(default_factory=list)
|
btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sector_size(self) -> SectorSize:
|
||||||
|
sector_size = self.partition.geometry.device.sectorSize
|
||||||
|
return SectorSize(sector_size, Unit.B)
|
||||||
|
|
||||||
def table_data(self) -> Dict[str, Any]:
|
def table_data(self) -> Dict[str, Any]:
|
||||||
|
end = self.start + self.length
|
||||||
|
|
||||||
part_info = {
|
part_info = {
|
||||||
'Name': self.name,
|
'Name': self.name,
|
||||||
'Type': self.type.value,
|
'Type': self.type.value,
|
||||||
'Filesystem': self.fs_type.value if self.fs_type else str(_('Unknown')),
|
'Filesystem': self.fs_type.value if self.fs_type else str(_('Unknown')),
|
||||||
'Path': str(self.path),
|
'Path': str(self.path),
|
||||||
'Start': self.start.format_size(Unit.MiB),
|
'Start': self.start.format_size(Unit.sectors, self.sector_size, include_unit=False),
|
||||||
'Length': self.length.format_size(Unit.MiB),
|
'End': end.format_size(Unit.sectors, self.sector_size, include_unit=False),
|
||||||
|
'Size': self.length.format_highest(),
|
||||||
'Flags': ', '.join([f.name for f in self.flags])
|
'Flags': ', '.join([f.name for f in self.flags])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,10 +358,14 @@ class _PartitionInfo:
|
||||||
start = Size(
|
start = Size(
|
||||||
partition.geometry.start,
|
partition.geometry.start,
|
||||||
Unit.sectors,
|
Unit.sectors,
|
||||||
Size(partition.disk.device.sectorSize, Unit.B)
|
SectorSize(partition.disk.device.sectorSize, Unit.B)
|
||||||
)
|
)
|
||||||
|
|
||||||
length = Size(int(partition.getLength(unit='B')), Unit.B)
|
length = Size(
|
||||||
|
int(partition.getLength(unit='B')),
|
||||||
|
Unit.B,
|
||||||
|
SectorSize(partition.disk.device.sectorSize, Unit.B)
|
||||||
|
)
|
||||||
|
|
||||||
return _PartitionInfo(
|
return _PartitionInfo(
|
||||||
partition=partition,
|
partition=partition,
|
||||||
|
|
@ -355,7 +390,7 @@ class _DeviceInfo:
|
||||||
type: str
|
type: str
|
||||||
total_size: Size
|
total_size: Size
|
||||||
free_space_regions: List[DeviceGeometry]
|
free_space_regions: List[DeviceGeometry]
|
||||||
sector_size: Size
|
sector_size: SectorSize
|
||||||
read_only: bool
|
read_only: bool
|
||||||
dirty: bool
|
dirty: bool
|
||||||
|
|
||||||
|
|
@ -365,7 +400,7 @@ class _DeviceInfo:
|
||||||
'Model': self.model,
|
'Model': self.model,
|
||||||
'Path': str(self.path),
|
'Path': str(self.path),
|
||||||
'Type': self.type,
|
'Type': self.type,
|
||||||
'Size': self.total_size.format_size(Unit.MiB),
|
'Size': self.total_size.format_highest(),
|
||||||
'Free space': int(total_free_space),
|
'Free space': int(total_free_space),
|
||||||
'Sector size': self.sector_size.value,
|
'Sector size': self.sector_size.value,
|
||||||
'Read only': self.read_only
|
'Read only': self.read_only
|
||||||
|
|
@ -379,15 +414,17 @@ class _DeviceInfo:
|
||||||
else:
|
else:
|
||||||
device_type = parted.devices[device.type]
|
device_type = parted.devices[device.type]
|
||||||
|
|
||||||
sector_size = Size(device.sectorSize, Unit.B)
|
sector_size = SectorSize(device.sectorSize, Unit.B)
|
||||||
free_space = [DeviceGeometry(g, sector_size) for g in disk.getFreeSpaceRegions()]
|
free_space = [DeviceGeometry(g, sector_size) for g in disk.getFreeSpaceRegions()]
|
||||||
|
|
||||||
|
sector_size = SectorSize(device.sectorSize, Unit.B)
|
||||||
|
|
||||||
return _DeviceInfo(
|
return _DeviceInfo(
|
||||||
model=device.model.strip(),
|
model=device.model.strip(),
|
||||||
path=Path(device.path),
|
path=Path(device.path),
|
||||||
type=device_type,
|
type=device_type,
|
||||||
sector_size=sector_size,
|
sector_size=sector_size,
|
||||||
total_size=Size(int(device.getLength(unit='B')), Unit.B),
|
total_size=Size(int(device.getLength(unit='B')), Unit.B, sector_size),
|
||||||
free_space_regions=free_space,
|
free_space_regions=free_space,
|
||||||
read_only=device.readOnly,
|
read_only=device.readOnly,
|
||||||
dirty=device.dirty
|
dirty=device.dirty
|
||||||
|
|
@ -470,7 +507,7 @@ class SubvolumeModification:
|
||||||
|
|
||||||
|
|
||||||
class DeviceGeometry:
|
class DeviceGeometry:
|
||||||
def __init__(self, geometry: Geometry, sector_size: Size):
|
def __init__(self, geometry: Geometry, sector_size: SectorSize):
|
||||||
self._geometry = geometry
|
self._geometry = geometry
|
||||||
self._sector_size = sector_size
|
self._sector_size = sector_size
|
||||||
|
|
||||||
|
|
@ -498,7 +535,7 @@ class DeviceGeometry:
|
||||||
'Sector size': self._sector_size.value,
|
'Sector size': self._sector_size.value,
|
||||||
'Start (sector/B)': start_str,
|
'Start (sector/B)': start_str,
|
||||||
'End (sector/B)': end_str,
|
'End (sector/B)': end_str,
|
||||||
'Length (sectors/B)': length_str
|
'Size (sectors/B)': length_str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -751,7 +788,7 @@ class PartitionModification:
|
||||||
'status': self.status.value,
|
'status': self.status.value,
|
||||||
'type': self.type.value,
|
'type': self.type.value,
|
||||||
'start': self.start.json(),
|
'start': self.start.json(),
|
||||||
'length': self.length.json(),
|
'size': self.length.json(),
|
||||||
'fs_type': self.fs_type.value if self.fs_type else '',
|
'fs_type': self.fs_type.value if self.fs_type else '',
|
||||||
'mountpoint': str(self.mountpoint) if self.mountpoint else None,
|
'mountpoint': str(self.mountpoint) if self.mountpoint else None,
|
||||||
'mount_options': self.mount_options,
|
'mount_options': self.mount_options,
|
||||||
|
|
@ -764,12 +801,15 @@ class PartitionModification:
|
||||||
"""
|
"""
|
||||||
Called for displaying data in table format
|
Called for displaying data in table format
|
||||||
"""
|
"""
|
||||||
|
end = self.start + self.length
|
||||||
|
|
||||||
part_mod = {
|
part_mod = {
|
||||||
'Status': self.status.value,
|
'Status': self.status.value,
|
||||||
'Device': str(self.dev_path) if self.dev_path else '',
|
'Device': str(self.dev_path) if self.dev_path else '',
|
||||||
'Type': self.type.value,
|
'Type': self.type.value,
|
||||||
'Start': self.start.format_size(Unit.MiB),
|
'Start': self.start.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||||
'Length': self.length.format_size(Unit.MiB),
|
'End': end.format_size(Unit.sectors, self.start.sector_size, include_unit=False),
|
||||||
|
'Size': self.length.format_highest(),
|
||||||
'FS type': self.fs_type.value if self.fs_type else 'Unknown',
|
'FS type': self.fs_type.value if self.fs_type else 'Unknown',
|
||||||
'Mountpoint': self.mountpoint if self.mountpoint else '',
|
'Mountpoint': self.mountpoint if self.mountpoint else '',
|
||||||
'Mount options': ', '.join(self.mount_options),
|
'Mount options': ', '.join(self.mount_options),
|
||||||
|
|
@ -938,7 +978,7 @@ class LsblkInfo:
|
||||||
name: str = ''
|
name: str = ''
|
||||||
path: Path = Path()
|
path: Path = Path()
|
||||||
pkname: str = ''
|
pkname: str = ''
|
||||||
size: Size = field(default_factory=lambda: Size(0, Unit.B))
|
size: Size = field(default_factory=lambda: Size(0, Unit.B, SectorSize.default()))
|
||||||
log_sec: int = 0
|
log_sec: int = 0
|
||||||
pttype: str = ''
|
pttype: str = ''
|
||||||
ptuuid: str = ''
|
ptuuid: str = ''
|
||||||
|
|
@ -1017,7 +1057,8 @@ class LsblkInfo:
|
||||||
if isinstance(getattr(lsblk_info, data_field), Path):
|
if isinstance(getattr(lsblk_info, data_field), Path):
|
||||||
val = Path(blockdevice[lsblk_field])
|
val = Path(blockdevice[lsblk_field])
|
||||||
elif isinstance(getattr(lsblk_info, data_field), Size):
|
elif isinstance(getattr(lsblk_info, data_field), Size):
|
||||||
val = Size(blockdevice[lsblk_field], Unit.B)
|
sector_size = SectorSize(blockdevice['log-sec'], Unit.B)
|
||||||
|
val = Size(blockdevice[lsblk_field], Unit.B, sector_size)
|
||||||
else:
|
else:
|
||||||
val = blockdevice[lsblk_field]
|
val = blockdevice[lsblk_field]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||||
from typing import Any, Dict, TYPE_CHECKING, List, Optional, Tuple
|
from typing import Any, Dict, TYPE_CHECKING, List, Optional, Tuple
|
||||||
|
|
||||||
from .device_model import PartitionModification, FilesystemType, BDevice, Size, Unit, PartitionType, PartitionFlag, \
|
from .device_model import PartitionModification, FilesystemType, BDevice, Size, Unit, PartitionType, PartitionFlag, \
|
||||||
ModificationStatus, DeviceGeometry
|
ModificationStatus, DeviceGeometry, SectorSize
|
||||||
from ..menu import Menu, ListManager, MenuSelection, TextInput
|
from ..menu import Menu, ListManager, MenuSelection, TextInput
|
||||||
from ..output import FormattedOutput, warn
|
from ..output import FormattedOutput, warn
|
||||||
from .subvolume_menu import SubvolumeMenu
|
from .subvolume_menu import SubvolumeMenu
|
||||||
|
|
@ -194,42 +194,47 @@ class PartitioningList(ListManager):
|
||||||
|
|
||||||
def _validate_value(
|
def _validate_value(
|
||||||
self,
|
self,
|
||||||
sector_size: Size,
|
sector_size: SectorSize,
|
||||||
total_size: Size,
|
total_size: Size,
|
||||||
value: str
|
text: str,
|
||||||
|
start: Optional[Size]
|
||||||
) -> Optional[Size]:
|
) -> Optional[Size]:
|
||||||
match = re.match(r'([0-9]+)([a-zA-Z|%]*)', value, re.I)
|
match = re.match(r'([0-9]+)([a-zA-Z|%]*)', text, re.I)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
value, unit = match.groups()
|
str_value, unit = match.groups()
|
||||||
|
|
||||||
if unit == '%':
|
if unit == '%' and start:
|
||||||
unit = Unit.Percent.name
|
available = total_size - start
|
||||||
|
value = int(available.value * (int(str_value) / 100))
|
||||||
|
unit = available.unit.name
|
||||||
|
else:
|
||||||
|
value = int(str_value)
|
||||||
|
|
||||||
if unit and unit not in Unit.get_all_units():
|
if unit and unit not in Unit.get_all_units():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
unit = Unit[unit] if unit else Unit.sectors
|
unit = Unit[unit] if unit else Unit.sectors
|
||||||
return Size(int(value), unit, sector_size, total_size)
|
return Size(value, unit, sector_size)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _enter_size(
|
def _enter_size(
|
||||||
self,
|
self,
|
||||||
sector_size: Size,
|
sector_size: SectorSize,
|
||||||
total_size: Size,
|
total_size: Size,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
default: Size
|
default: Size,
|
||||||
|
start: Optional[Size],
|
||||||
) -> Size:
|
) -> Size:
|
||||||
while True:
|
while True:
|
||||||
value = TextInput(prompt).run().strip()
|
value = TextInput(prompt).run().strip()
|
||||||
|
|
||||||
size: Optional[Size] = None
|
size: Optional[Size] = None
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
size = default
|
size = default
|
||||||
else:
|
else:
|
||||||
size = self._validate_value(sector_size, total_size, value)
|
size = self._validate_value(sector_size, total_size, value, start)
|
||||||
|
|
||||||
if size:
|
if size:
|
||||||
return size
|
return size
|
||||||
|
|
@ -247,7 +252,7 @@ class PartitioningList(ListManager):
|
||||||
total_bytes = device_info.total_size.format_size(Unit.B)
|
total_bytes = device_info.total_size.format_size(Unit.B)
|
||||||
|
|
||||||
prompt += str(_('Total: {} / {}')).format(total_sectors, total_bytes) + '\n\n'
|
prompt += str(_('Total: {} / {}')).format(total_sectors, total_bytes) + '\n\n'
|
||||||
prompt += str(_('All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB...')) + '\n'
|
prompt += str(_('All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...')) + '\n'
|
||||||
prompt += str(_('If no unit is provided, the value is interpreted as sectors')) + '\n'
|
prompt += str(_('If no unit is provided, the value is interpreted as sectors')) + '\n'
|
||||||
print(prompt)
|
print(prompt)
|
||||||
|
|
||||||
|
|
@ -260,13 +265,14 @@ class PartitioningList(ListManager):
|
||||||
device_info.sector_size,
|
device_info.sector_size,
|
||||||
device_info.total_size,
|
device_info.total_size,
|
||||||
start_prompt,
|
start_prompt,
|
||||||
default_start
|
default_start,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
if start_size.value == largest_free_area.start:
|
if start_size.value == largest_free_area.start:
|
||||||
end_size = Size(largest_free_area.end, Unit.sectors, device_info.sector_size)
|
end_size = Size(largest_free_area.end, Unit.sectors, device_info.sector_size)
|
||||||
else:
|
else:
|
||||||
end_size = Size(100, Unit.Percent, total_size=device_info.total_size)
|
end_size = device_info.total_size
|
||||||
|
|
||||||
# prompt until valid end sector was entered
|
# prompt until valid end sector was entered
|
||||||
end_prompt = str(_('Enter end (default: {}): ')).format(end_size.as_text())
|
end_prompt = str(_('Enter end (default: {}): ')).format(end_size.as_text())
|
||||||
|
|
@ -274,7 +280,8 @@ class PartitioningList(ListManager):
|
||||||
device_info.sector_size,
|
device_info.sector_size,
|
||||||
device_info.total_size,
|
device_info.total_size,
|
||||||
end_prompt,
|
end_prompt,
|
||||||
end_size
|
end_size,
|
||||||
|
start_size
|
||||||
)
|
)
|
||||||
|
|
||||||
return start_size, end_size
|
return start_size, end_size
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ class Installer:
|
||||||
lsblk_info = disk.get_lsblk_by_mountpoint(boot_mount)
|
lsblk_info = disk.get_lsblk_by_mountpoint(boot_mount)
|
||||||
|
|
||||||
if len(lsblk_info) > 0:
|
if len(lsblk_info) > 0:
|
||||||
if lsblk_info[0].size < disk.Size(200, disk.Unit.MiB):
|
if lsblk_info[0].size < disk.Size(200, disk.Unit.MiB, disk.SectorSize.default()):
|
||||||
raise DiskError(
|
raise DiskError(
|
||||||
f'The boot partition mounted at {boot_mount} is not large enough to install a boot loader. '
|
f'The boot partition mounted at {boot_mount} is not large enough to install a boot loader. '
|
||||||
f'Please resize it to at least 200MiB and re-run the installation.'
|
f'Please resize it to at least 200MiB and re-run the installation.'
|
||||||
|
|
|
||||||
|
|
@ -170,13 +170,13 @@ def select_disk_config(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _boot_partition() -> disk.PartitionModification:
|
def _boot_partition(sector_size: disk.SectorSize) -> disk.PartitionModification:
|
||||||
if SysInfo.has_uefi():
|
if SysInfo.has_uefi():
|
||||||
start = disk.Size(1, disk.Unit.MiB)
|
start = disk.Size(1, disk.Unit.MiB, sector_size)
|
||||||
size = disk.Size(512, disk.Unit.MiB)
|
size = disk.Size(512, disk.Unit.MiB, sector_size)
|
||||||
else:
|
else:
|
||||||
start = disk.Size(3, disk.Unit.MiB)
|
start = disk.Size(3, disk.Unit.MiB, sector_size)
|
||||||
size = disk.Size(203, disk.Unit.MiB)
|
size = disk.Size(203, disk.Unit.MiB, sector_size)
|
||||||
|
|
||||||
# boot partition
|
# boot partition
|
||||||
return disk.PartitionModification(
|
return disk.PartitionModification(
|
||||||
|
|
@ -215,8 +215,9 @@ def suggest_single_disk_layout(
|
||||||
if not filesystem_type:
|
if not filesystem_type:
|
||||||
filesystem_type = select_main_filesystem_format(advanced_options)
|
filesystem_type = select_main_filesystem_format(advanced_options)
|
||||||
|
|
||||||
min_size_to_allow_home_part = disk.Size(40, disk.Unit.GiB)
|
sector_size = device.device_info.sector_size
|
||||||
root_partition_size = disk.Size(20, disk.Unit.GiB)
|
min_size_to_allow_home_part = disk.Size(40, disk.Unit.GiB, sector_size)
|
||||||
|
root_partition_size = disk.Size(20, disk.Unit.GiB, sector_size)
|
||||||
using_subvolumes = False
|
using_subvolumes = False
|
||||||
using_home_partition = False
|
using_home_partition = False
|
||||||
compression = False
|
compression = False
|
||||||
|
|
@ -244,7 +245,7 @@ def suggest_single_disk_layout(
|
||||||
# Also re-align the start to 1MiB since we don't need the first sectors
|
# Also re-align the start to 1MiB since we don't need the first sectors
|
||||||
# like we do in MBR layouts where the boot loader is installed traditionally.
|
# like we do in MBR layouts where the boot loader is installed traditionally.
|
||||||
|
|
||||||
boot_partition = _boot_partition()
|
boot_partition = _boot_partition(sector_size)
|
||||||
device_modification.add_partition(boot_partition)
|
device_modification.add_partition(boot_partition)
|
||||||
|
|
||||||
if not using_subvolumes:
|
if not using_subvolumes:
|
||||||
|
|
@ -259,11 +260,11 @@ def suggest_single_disk_layout(
|
||||||
using_home_partition = False
|
using_home_partition = False
|
||||||
|
|
||||||
# root partition
|
# root partition
|
||||||
start = disk.Size(513, disk.Unit.MiB) if SysInfo.has_uefi() else disk.Size(206, disk.Unit.MiB)
|
start = disk.Size(513, disk.Unit.MiB, sector_size) if SysInfo.has_uefi() else disk.Size(206, disk.Unit.MiB, sector_size)
|
||||||
|
|
||||||
# Set a size for / (/root)
|
# Set a size for / (/root)
|
||||||
if using_subvolumes or device_size_gib < min_size_to_allow_home_part or not using_home_partition:
|
if using_subvolumes or device_size_gib < min_size_to_allow_home_part or not using_home_partition:
|
||||||
length = disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size)
|
length = device.device_info.total_size - start
|
||||||
else:
|
else:
|
||||||
length = min(device.device_info.total_size, root_partition_size)
|
length = min(device.device_info.total_size, root_partition_size)
|
||||||
|
|
||||||
|
|
@ -294,11 +295,14 @@ def suggest_single_disk_layout(
|
||||||
# If we don't want to use subvolumes,
|
# If we don't want to use subvolumes,
|
||||||
# But we want to be able to re-use data between re-installs..
|
# But we want to be able to re-use data between re-installs..
|
||||||
# A second partition for /home would be nice if we have the space for it
|
# A second partition for /home would be nice if we have the space for it
|
||||||
|
start = root_partition.length
|
||||||
|
length = device.device_info.total_size - root_partition.length
|
||||||
|
|
||||||
home_partition = disk.PartitionModification(
|
home_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=root_partition.length,
|
start=start,
|
||||||
length=disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size),
|
length=length,
|
||||||
mountpoint=Path('/home'),
|
mountpoint=Path('/home'),
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
mount_options=['compress=zstd'] if compression else []
|
mount_options=['compress=zstd'] if compression else []
|
||||||
|
|
@ -319,9 +323,9 @@ def suggest_multi_disk_layout(
|
||||||
# Not really a rock solid foundation of information to stand on, but it's a start:
|
# Not really a rock solid foundation of information to stand on, but it's a start:
|
||||||
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
|
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
|
||||||
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
|
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
|
||||||
min_home_partition_size = disk.Size(40, disk.Unit.GiB)
|
min_home_partition_size = disk.Size(40, disk.Unit.GiB, disk.SectorSize.default())
|
||||||
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
|
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
|
||||||
desired_root_partition_size = disk.Size(20, disk.Unit.GiB)
|
desired_root_partition_size = disk.Size(20, disk.Unit.GiB, disk.SectorSize.default())
|
||||||
compression = False
|
compression = False
|
||||||
|
|
||||||
if not filesystem_type:
|
if not filesystem_type:
|
||||||
|
|
@ -362,28 +366,41 @@ def suggest_multi_disk_layout(
|
||||||
root_device_modification = disk.DeviceModification(root_device, wipe=True)
|
root_device_modification = disk.DeviceModification(root_device, wipe=True)
|
||||||
home_device_modification = disk.DeviceModification(home_device, wipe=True)
|
home_device_modification = disk.DeviceModification(home_device, wipe=True)
|
||||||
|
|
||||||
|
root_device_sector_size = root_device_modification.device.device_info.sector_size
|
||||||
|
home_device_sector_size = home_device_modification.device.device_info.sector_size
|
||||||
|
|
||||||
# add boot partition to the root device
|
# add boot partition to the root device
|
||||||
boot_partition = _boot_partition()
|
boot_partition = _boot_partition(root_device_sector_size)
|
||||||
root_device_modification.add_partition(boot_partition)
|
root_device_modification.add_partition(boot_partition)
|
||||||
|
|
||||||
|
if SysInfo.has_uefi():
|
||||||
|
root_start = disk.Size(513, disk.Unit.MiB, root_device_sector_size)
|
||||||
|
else:
|
||||||
|
root_start = disk.Size(206, disk.Unit.MiB, root_device_sector_size)
|
||||||
|
|
||||||
|
root_length = root_device.device_info.total_size - root_start
|
||||||
|
|
||||||
# add root partition to the root device
|
# add root partition to the root device
|
||||||
root_partition = disk.PartitionModification(
|
root_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=disk.Size(513, disk.Unit.MiB) if SysInfo.has_uefi() else disk.Size(206, disk.Unit.MiB),
|
start=root_start,
|
||||||
length=disk.Size(100, disk.Unit.Percent, total_size=root_device.device_info.total_size),
|
length=root_length,
|
||||||
mountpoint=Path('/'),
|
mountpoint=Path('/'),
|
||||||
mount_options=['compress=zstd'] if compression else [],
|
mount_options=['compress=zstd'] if compression else [],
|
||||||
fs_type=filesystem_type
|
fs_type=filesystem_type
|
||||||
)
|
)
|
||||||
root_device_modification.add_partition(root_partition)
|
root_device_modification.add_partition(root_partition)
|
||||||
|
|
||||||
|
start = disk.Size(1, disk.Unit.MiB, home_device_sector_size)
|
||||||
|
length = home_device.device_info.total_size - start
|
||||||
|
|
||||||
# add home partition to home device
|
# add home partition to home device
|
||||||
home_partition = disk.PartitionModification(
|
home_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=disk.Size(1, disk.Unit.MiB),
|
start=start,
|
||||||
length=disk.Size(100, disk.Unit.Percent, total_size=home_device.device_info.total_size),
|
length=length,
|
||||||
mountpoint=Path('/home'),
|
mountpoint=Path('/home'),
|
||||||
mount_options=['compress=zstd'] if compression else [],
|
mount_options=['compress=zstd'] if compression else [],
|
||||||
fs_type=filesystem_type,
|
fs_type=filesystem_type,
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,8 @@
|
||||||
"Boot"
|
"Boot"
|
||||||
],
|
],
|
||||||
"fs_type": "fat32",
|
"fs_type": "fat32",
|
||||||
"length": {
|
"size": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": null,
|
|
||||||
"unit": "MiB",
|
"unit": "MiB",
|
||||||
"value": 512
|
"value": 512
|
||||||
},
|
},
|
||||||
|
|
@ -28,7 +27,6 @@
|
||||||
"obj_id": "2c3fa2d5-2c79-4fab-86ec-22d0ea1543c0",
|
"obj_id": "2c3fa2d5-2c79-4fab-86ec-22d0ea1543c0",
|
||||||
"start": {
|
"start": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": null,
|
|
||||||
"unit": "MiB",
|
"unit": "MiB",
|
||||||
"value": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
|
|
@ -39,9 +37,8 @@
|
||||||
"btrfs": [],
|
"btrfs": [],
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"fs_type": "ext4",
|
"fs_type": "ext4",
|
||||||
"length": {
|
"size": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": null,
|
|
||||||
"unit": "GiB",
|
"unit": "GiB",
|
||||||
"value": 20
|
"value": 20
|
||||||
},
|
},
|
||||||
|
|
@ -50,7 +47,6 @@
|
||||||
"obj_id": "3e7018a0-363b-4d05-ab83-8e82d13db208",
|
"obj_id": "3e7018a0-363b-4d05-ab83-8e82d13db208",
|
||||||
"start": {
|
"start": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": null,
|
|
||||||
"unit": "MiB",
|
"unit": "MiB",
|
||||||
"value": 513
|
"value": 513
|
||||||
},
|
},
|
||||||
|
|
@ -61,14 +57,8 @@
|
||||||
"btrfs": [],
|
"btrfs": [],
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"fs_type": "ext4",
|
"fs_type": "ext4",
|
||||||
"length": {
|
"size": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": {
|
|
||||||
"sector_size": null,
|
|
||||||
"total_size": null,
|
|
||||||
"unit": "B",
|
|
||||||
"value": 250148290560
|
|
||||||
},
|
|
||||||
"unit": "Percent",
|
"unit": "Percent",
|
||||||
"value": 100
|
"value": 100
|
||||||
},
|
},
|
||||||
|
|
@ -77,7 +67,6 @@
|
||||||
"obj_id": "ce58b139-f041-4a06-94da-1f8bad775d3f",
|
"obj_id": "ce58b139-f041-4a06-94da-1f8bad775d3f",
|
||||||
"start": {
|
"start": {
|
||||||
"sector_size": null,
|
"sector_size": null,
|
||||||
"total_size": null,
|
|
||||||
"unit": "GiB",
|
"unit": "GiB",
|
||||||
"value": 20
|
"value": 20
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ device_modification = disk.DeviceModification(device, wipe=True)
|
||||||
boot_partition = disk.PartitionModification(
|
boot_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=disk.Size(1, disk.Unit.MiB),
|
start=disk.Size(1, disk.Unit.MiB, device.device_info.sector_size),
|
||||||
length=disk.Size(512, disk.Unit.MiB),
|
length=disk.Size(512, disk.Unit.MiB, device.device_info.sector_size),
|
||||||
mountpoint=Path('/boot'),
|
mountpoint=Path('/boot'),
|
||||||
fs_type=disk.FilesystemType.Fat32,
|
fs_type=disk.FilesystemType.Fat32,
|
||||||
flags=[disk.PartitionFlag.Boot]
|
flags=[disk.PartitionFlag.Boot]
|
||||||
|
|
@ -35,20 +35,23 @@ device_modification.add_partition(boot_partition)
|
||||||
root_partition = disk.PartitionModification(
|
root_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=disk.Size(513, disk.Unit.MiB),
|
start=disk.Size(513, disk.Unit.MiB, device.device_info.sector_size),
|
||||||
length=disk.Size(20, disk.Unit.GiB),
|
length=disk.Size(20, disk.Unit.GiB, device.device_info.sector_size),
|
||||||
mountpoint=None,
|
mountpoint=None,
|
||||||
fs_type=fs_type,
|
fs_type=fs_type,
|
||||||
mount_options=[],
|
mount_options=[],
|
||||||
)
|
)
|
||||||
device_modification.add_partition(root_partition)
|
device_modification.add_partition(root_partition)
|
||||||
|
|
||||||
|
start_home = root_partition.length
|
||||||
|
length_home = device.device_info.total_size - start_home
|
||||||
|
|
||||||
# create a new home partition
|
# create a new home partition
|
||||||
home_partition = disk.PartitionModification(
|
home_partition = disk.PartitionModification(
|
||||||
status=disk.ModificationStatus.Create,
|
status=disk.ModificationStatus.Create,
|
||||||
type=disk.PartitionType.Primary,
|
type=disk.PartitionType.Primary,
|
||||||
start=root_partition.length,
|
start=start_home,
|
||||||
length=disk.Size(100, disk.Unit.Percent, total_size=device.device_info.total_size),
|
length=length_home,
|
||||||
mountpoint=Path('/home'),
|
mountpoint=Path('/home'),
|
||||||
fs_type=fs_type,
|
fs_type=fs_type,
|
||||||
mount_options=[]
|
mount_options=[]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue