More work on partition stuff #2, added BlkSize struct to handle partition sizes nicely.
This commit is contained in:
@@ -9,6 +9,7 @@ pub fn color_bar<Message: 'static>(
|
||||
let mut c_b = Row::new().height(bar_height);
|
||||
|
||||
if let Some(items) = maybe_items {
|
||||
|
||||
for (bar_text, bar_fill, bar_color) in items {
|
||||
c_b = c_b.push(
|
||||
container(text(bar_text).color(Color::BLACK).size(iced::Pixels(14.0)))
|
||||
@@ -16,9 +17,13 @@ pub fn color_bar<Message: 'static>(
|
||||
.width(Length::FillPortion(bar_fill))
|
||||
.style(move |_| container::background(bar_color))
|
||||
.align_x(Alignment::Center)
|
||||
.align_y(Alignment::Center),
|
||||
.align_y(Alignment::Center)
|
||||
.padding(2),
|
||||
);
|
||||
}
|
||||
}
|
||||
c_b.into()
|
||||
container(c_b.spacing(2))
|
||||
.style(container::rounded_box)
|
||||
.padding(3)
|
||||
.into()
|
||||
}
|
||||
|
||||
+168
-25
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
pub fn format_bytes_size(size: u64) -> String {
|
||||
let suffixs: [&str; 5] = ["KB", "MB", "GB", "TB", "PB"];
|
||||
@@ -19,35 +20,124 @@ pub fn format_bytes_size(size: u64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BlkSize {
|
||||
size_bytes: u64,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BlkSize {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.size_bytes < 1000 {
|
||||
write!(f, "{}B", self.size_bytes)
|
||||
} else {
|
||||
let mut s = self.size_bytes as f64;
|
||||
let mut idx = 0;
|
||||
for i in 0..5 {
|
||||
s = s / 1024.0;
|
||||
idx = i;
|
||||
if s < 1000.0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write!(f, "{:.2}{}", s, Self::SUFFIXS[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlkSize {
|
||||
pub const SUFFIXS: [&str; 5] = ["KB", "MB", "GB", "TB", "PB"];
|
||||
pub fn new(size:u64) -> Self {
|
||||
Self { size_bytes: size }
|
||||
}
|
||||
pub fn new_kb(size:u64) -> Self {
|
||||
Self { size_bytes: size * 1024 }
|
||||
}
|
||||
pub fn new_mb(size:u64) -> Self {
|
||||
Self { size_bytes: size * 1024u64.pow(2) }
|
||||
}
|
||||
pub fn new_gb(size:u64) -> Self {
|
||||
Self { size_bytes: size * 1024u64.pow(3) }
|
||||
}
|
||||
pub fn new_tb(size:u64) -> Self {
|
||||
Self { size_bytes: size * 1024u64.pow(4) }
|
||||
}
|
||||
pub fn new_pb(size:u64) -> Self {
|
||||
Self { size_bytes: size * 1024u64.pow(5) }
|
||||
}
|
||||
|
||||
pub fn b(&self) -> u64 {
|
||||
self.size_bytes
|
||||
}
|
||||
pub fn kb(&self) -> u64 {
|
||||
self.size_bytes / 1024u64.pow(2)
|
||||
}
|
||||
pub fn mb(&self) -> u64 {
|
||||
self.size_bytes / 1024u64.pow(3)
|
||||
}
|
||||
pub fn gb(&self) -> u64 {
|
||||
self.size_bytes / 1024u64.pow(4)
|
||||
}
|
||||
pub fn tb(&self) -> u64 {
|
||||
self.size_bytes / 1024u64.pow(5)
|
||||
}
|
||||
pub fn pb(&self) -> u64 {
|
||||
self.size_bytes / 1024u64.pow(6)
|
||||
}
|
||||
|
||||
pub fn from_str_bytes(s: &str) -> Option<Self> {
|
||||
Some(Self::new(u64::from_str_radix(s, 10).ok()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for BlkSize {
|
||||
type Output = BlkSize;
|
||||
|
||||
fn add(self, other: BlkSize) -> BlkSize {
|
||||
BlkSize {
|
||||
size_bytes: self.size_bytes + other.size_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for BlkSize {
|
||||
type Output = BlkSize;
|
||||
|
||||
fn sub(self, other: BlkSize) -> BlkSize {
|
||||
BlkSize {
|
||||
size_bytes: self.size_bytes.strict_sub(other.size_bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// size - device size in bytes
|
||||
/// sector size - default 4096
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BlkDev {
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub sector_size: u32,
|
||||
pub size: BlkSize,
|
||||
pub sector_size: BlkSize,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BlkDev {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} ({})", &self.name, format_bytes_size(self.size))
|
||||
write!(f, "{} ({})", &self.name, self.size)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlkDev {
|
||||
pub fn new(name: String, size: u64) -> Self {
|
||||
pub fn new(name: String, size: BlkSize) -> Self {
|
||||
Self {
|
||||
name: name,
|
||||
size: size,
|
||||
sector_size: 4096,
|
||||
sector_size: BlkSize::new(4096),
|
||||
}
|
||||
}
|
||||
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
||||
Some(Self {
|
||||
name: data.get("NAME")?.clone(),
|
||||
size: u64::from_str_radix(data.get("SIZE")?, 10).ok()?,
|
||||
sector_size: 4096,
|
||||
size: BlkSize::from_str_bytes(data.get("SIZE")?)?,
|
||||
sector_size: BlkSize::new(4096),
|
||||
})
|
||||
}
|
||||
/// this is blocking function
|
||||
@@ -151,15 +241,26 @@ impl FSType {
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PartRole {
|
||||
EFI,
|
||||
ROOT,
|
||||
HOME,
|
||||
SWAP,
|
||||
LuksRoot,
|
||||
LuksHome,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PartInfo {
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub start: u64,
|
||||
pub size: BlkSize,
|
||||
pub start: BlkSize,
|
||||
pub fs_type: FSType,
|
||||
pub gpt_label: Option<String>,
|
||||
pub fs_label: Option<String>,
|
||||
pub mount_point: Option<String>,
|
||||
pub role: Option<PartRole>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PartInfo {
|
||||
@@ -169,7 +270,7 @@ impl std::fmt::Display for PartInfo {
|
||||
"{} {} {}",
|
||||
&self.name,
|
||||
&self.fs_type,
|
||||
format_bytes_size(self.size)
|
||||
self.size
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -178,8 +279,8 @@ impl PartInfo {
|
||||
pub fn from_hash_map(data: &HashMap<String, String>) -> Option<Self> {
|
||||
Some(Self {
|
||||
name: data.get("NAME")?.clone(),
|
||||
size: u64::from_str_radix(data.get("SIZE")?, 10).ok()?,
|
||||
start: 0,
|
||||
size: BlkSize::from_str_bytes(data.get("SIZE")?)?,
|
||||
start: BlkSize::new(0),
|
||||
fs_type: FSType::from_str(data.get("FSTYPE")?),
|
||||
gpt_label: data
|
||||
.get("PARTLABEL")
|
||||
@@ -190,6 +291,7 @@ impl PartInfo {
|
||||
mount_point: data
|
||||
.get("MOUNTPOINT")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v.clone()) }),
|
||||
role: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,7 +389,7 @@ pub fn align_part(size: u64, start: u64, align: u64) -> Option<(u64, u64, u64)>
|
||||
|
||||
pub struct PartLayout {
|
||||
pub dev: BlkDev,
|
||||
pub empty_space: u64,
|
||||
pub empty_space: BlkSize,
|
||||
pub part_list: Vec<PartInfo>,
|
||||
}
|
||||
|
||||
@@ -302,7 +404,7 @@ impl PartLayout {
|
||||
|
||||
pub fn read_from_disk(dev: &BlkDev) -> Result<Self, String> {
|
||||
let part_list = get_dev_part_blocking(&dev.name)?;
|
||||
let parts_size = part_list.iter().fold(0, |acc, part| acc + part.size);
|
||||
let parts_size = part_list.iter().fold(BlkSize::new(0), |acc, part| acc + part.size);
|
||||
Ok(Self {
|
||||
dev: dev.clone(),
|
||||
empty_space: dev.size - parts_size,
|
||||
@@ -318,45 +420,86 @@ impl PartLayout {
|
||||
|
||||
pub fn add_part(
|
||||
mut self,
|
||||
part_size: u64,
|
||||
part_size: BlkSize,
|
||||
fs_type: FSType,
|
||||
gpt_label: Option<String>,
|
||||
fs_label: Option<String>,
|
||||
mount_point: Option<String>,
|
||||
role: Option<PartRole>,
|
||||
) -> Self {
|
||||
let part_start = if self.part_list.is_empty() {
|
||||
2u64 * 1024u64 * 1024u64
|
||||
BlkSize::new_mb(2)
|
||||
} else {
|
||||
self.dev.size.checked_sub(self.empty_space).unwrap()
|
||||
self.dev.size - self.empty_space
|
||||
};
|
||||
|
||||
let (size, start, _) =
|
||||
align_part(part_size, part_start, self.dev.sector_size as u64).unwrap();
|
||||
align_part(part_size.b(), part_start.b(), self.dev.sector_size.b()).unwrap();
|
||||
|
||||
let part_name = format!("{}p{}", self.dev.name, self.part_list.len() + 1);
|
||||
self.part_list.push(PartInfo {
|
||||
name: "_".into(),
|
||||
size: size,
|
||||
start: start,
|
||||
name: part_name,
|
||||
size: BlkSize::new(size),
|
||||
start: BlkSize::new(start),
|
||||
fs_type: fs_type,
|
||||
gpt_label: gpt_label,
|
||||
fs_label: fs_label,
|
||||
mount_point: mount_point,
|
||||
role: role,
|
||||
});
|
||||
|
||||
self.empty_space = self.empty_space.checked_sub(size).unwrap();
|
||||
self.empty_space = self.empty_space - BlkSize::new(size);
|
||||
|
||||
self
|
||||
}
|
||||
pub fn add_part_reserve(
|
||||
self,
|
||||
reserve_space: u64,
|
||||
reserve_space: BlkSize,
|
||||
fs_type: FSType,
|
||||
gpt_label: Option<String>,
|
||||
fs_label: Option<String>,
|
||||
mount_point: Option<String>,
|
||||
role: Option<PartRole>,
|
||||
) -> Self {
|
||||
let p_size = self.empty_space.checked_sub(reserve_space).unwrap();
|
||||
self.add_part(p_size, fs_type, gpt_label, fs_label, mount_point)
|
||||
let p_size = self.empty_space - reserve_space;
|
||||
self.add_part(p_size, fs_type, gpt_label, fs_label, mount_point, role)
|
||||
}
|
||||
|
||||
pub fn gen_classic_layout(dev: BlkDev, efi_size: BlkSize, swap_size: BlkSize) -> Self {
|
||||
let mut res: PartLayout = PartLayout::new(dev);
|
||||
|
||||
// UEFI partition
|
||||
res = res
|
||||
.add_part(
|
||||
efi_size,
|
||||
FSType::VFAT,
|
||||
Some("efi".into()),
|
||||
Some("EFI".into()),
|
||||
Some("/efi".into()),
|
||||
Some(PartRole::EFI),
|
||||
)
|
||||
.add_part_reserve(
|
||||
swap_size,
|
||||
FSType::XFS,
|
||||
Some("root".into()),
|
||||
None,
|
||||
Some("/".into()),
|
||||
Some(PartRole::ROOT),
|
||||
);
|
||||
|
||||
// swap
|
||||
if swap_size.b() > 0 {
|
||||
res = res.add_part(
|
||||
swap_size,
|
||||
FSType::SWAP,
|
||||
Some("swap".into()),
|
||||
None,
|
||||
Some("SWAP".into()),
|
||||
Some(PartRole::SWAP),
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+97
-50
@@ -21,10 +21,10 @@
|
||||
use crate::stage::{StageAction, StageResult};
|
||||
|
||||
use crate::kira_color_bar;
|
||||
use crate::kira_disk_layout::{BlkDev,PartInfo, PartLayout, FSType};
|
||||
use toml::Table;
|
||||
use iced::{Alignment, Color, Length, widget};
|
||||
use crate::kira_disk_layout::{BlkDev, BlkSize, FSType, PartInfo, PartLayout};
|
||||
use iced::{Alignment, Color, widget};
|
||||
use rust_i18n::t;
|
||||
use toml::Table;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SwapMode {
|
||||
@@ -61,13 +61,12 @@ fn part_type_to_color(t: &FSType) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn part_to_ct_params(part: &PartInfo, dev_size: u64) -> (String, u16, Color) {
|
||||
let fill_ratio: u16 = ((part.size as f32 / dev_size as f32) * 12.0) as u16;
|
||||
let fill_ratio = fill_ratio.max(1);
|
||||
let part_color = part_type_to_color(&part.fs_type);
|
||||
(part.to_string(), fill_ratio, part_color)
|
||||
}
|
||||
|
||||
fn part_to_ct_params(part: &PartInfo, dev_size: BlkSize) -> (String, u16, Color) {
|
||||
let fill_ratio: u16 = ((part.size.b() as f32 / dev_size.b() as f32) * 11.0) as u16;
|
||||
let fill_ratio = fill_ratio.max(1);
|
||||
let part_color = part_type_to_color(&part.fs_type);
|
||||
(part.to_string(), fill_ratio, part_color)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartitionStage {
|
||||
@@ -77,6 +76,7 @@ pub struct PartitionStage {
|
||||
use_zram: bool,
|
||||
secure_boot: bool,
|
||||
selected_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||
generated_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -89,32 +89,55 @@ pub enum Message {
|
||||
Back,
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl PartitionStage {
|
||||
pub fn gen_disk_layout_estimate(&self) -> PartLayout {
|
||||
let dev = self.device.clone().unwrap();
|
||||
// pub fn gen_disk_layout_estimate(&self) -> PartLayout {
|
||||
// let dev = self.device.clone().unwrap();
|
||||
|
||||
let swap_size: u64 = match self.swap_mode {
|
||||
Some(SwapMode::NoSwap) => 0,
|
||||
Some(SwapMode::SwapHibernate) => 666*1024*1024,
|
||||
Some(SwapMode::SwapNoHibernate) => 1024*1024*1024,
|
||||
_ => 1024*1024*1024,
|
||||
};
|
||||
let mut res: PartLayout = PartLayout::new(dev);
|
||||
// let swap_size: u64 = match self.swap_mode {
|
||||
// Some(SwapMode::NoSwap) => 0,
|
||||
// Some(SwapMode::SwapHibernate) => 666 * 1024 * 1024,
|
||||
// Some(SwapMode::SwapNoHibernate) => 1024 * 1024 * 1024,
|
||||
// _ => 1024 * 1024 * 1024,
|
||||
// };
|
||||
// let mut res: PartLayout = PartLayout::new(dev);
|
||||
|
||||
// UEFI partition
|
||||
res = res.add_part(256*1024*1024, FSType::VFAT, Some("kira-efi".into()), Some("EFI".into()), Some("/efi".into()))
|
||||
.add_part(512*1024*1024, FSType::XFS, Some("kira-boot-a".into()), None, Some("/".into()))
|
||||
.add_part_reserve(swap_size, FSType::XFS, Some("kira-home".into()), None, Some("/home".into()));
|
||||
|
||||
// swap
|
||||
if swap_size > 0 {
|
||||
res = res.add_part(swap_size, FSType::SWAP, Some("kira-swap".into()), None, Some("SWAP".into()));
|
||||
}
|
||||
// // UEFI partition
|
||||
// res = res
|
||||
// .add_part(
|
||||
// 256 * 1024 * 1024,
|
||||
// FSType::VFAT,
|
||||
// Some("kira-efi".into()),
|
||||
// Some("EFI".into()),
|
||||
// Some("/efi".into()),
|
||||
// )
|
||||
// .add_part(
|
||||
// 512 * 1024 * 1024,
|
||||
// FSType::XFS,
|
||||
// Some("kira-boot-a".into()),
|
||||
// None,
|
||||
// Some("/".into()),
|
||||
// )
|
||||
// .add_part_reserve(
|
||||
// swap_size,
|
||||
// FSType::XFS,
|
||||
// Some("kira-home".into()),
|
||||
// None,
|
||||
// Some("/home".into()),
|
||||
// );
|
||||
|
||||
res
|
||||
}
|
||||
// // swap
|
||||
// if swap_size > 0 {
|
||||
// res = res.add_part(
|
||||
// swap_size,
|
||||
// FSType::SWAP,
|
||||
// Some("kira-swap".into()),
|
||||
// None,
|
||||
// Some("SWAP".into()),
|
||||
// );
|
||||
// }
|
||||
|
||||
// res
|
||||
// }
|
||||
|
||||
pub fn new(toml_config: &Table) -> Result<Self, String> {
|
||||
let maybe_dev_list = BlkDev::list_sys_blk_dev();
|
||||
@@ -126,13 +149,13 @@ impl PartitionStage {
|
||||
.and_then(|v| v.as_integer())
|
||||
.unwrap_or(8192);
|
||||
|
||||
let min_disk_size: u64 = min_disk_size_mb.try_into().unwrap_or(8192) * 1024 * 1024;
|
||||
let min_disk_size: BlkSize = BlkSize::new_mb(min_disk_size_mb.try_into().unwrap_or(8192));
|
||||
|
||||
match maybe_dev_list {
|
||||
Ok(dev_list) => {
|
||||
let acepted_dev: Vec<BlkDev> = dev_list
|
||||
.iter()
|
||||
.filter(|v| v.size >= min_disk_size)
|
||||
.filter(|v| v.size.b() >= min_disk_size.b())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
@@ -143,6 +166,7 @@ impl PartitionStage {
|
||||
use_zram: true,
|
||||
secure_boot: false,
|
||||
selected_disk_parts: None,
|
||||
generated_disk_parts: None,
|
||||
})
|
||||
}
|
||||
Err(ex) => Err(ex),
|
||||
@@ -174,7 +198,8 @@ impl PartitionStage {
|
||||
Message::SelectDevice(dev) => {
|
||||
self.selected_disk_parts = match PartLayout::read_from_disk(&dev) {
|
||||
Ok(dev_parts) => Some(
|
||||
dev_parts.part_list
|
||||
dev_parts
|
||||
.part_list
|
||||
.iter()
|
||||
.map(|part_inf| part_to_ct_params(&part_inf, dev.size))
|
||||
.collect(),
|
||||
@@ -185,10 +210,44 @@ impl PartitionStage {
|
||||
}
|
||||
};
|
||||
|
||||
let swap_size: BlkSize = match self.swap_mode {
|
||||
Some(SwapMode::NoSwap) => BlkSize::new(0),
|
||||
Some(SwapMode::SwapHibernate) => BlkSize::new_gb(4),
|
||||
Some(SwapMode::SwapNoHibernate) => BlkSize::new_gb(2),
|
||||
_ => BlkSize::new_gb(2),
|
||||
};
|
||||
self.generated_disk_parts = Some(
|
||||
PartLayout::gen_classic_layout(dev.clone(), BlkSize::new_mb(512), swap_size)
|
||||
.part_list
|
||||
.iter()
|
||||
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
self.device = Some(dev);
|
||||
StageAction::None
|
||||
}
|
||||
Message::SelectSwapMode(sm) => {
|
||||
|
||||
if let Some(dev) = self.device.clone() {
|
||||
let swap_size: BlkSize = match sm {
|
||||
SwapMode::NoSwap => BlkSize::new(0),
|
||||
SwapMode::SwapHibernate => BlkSize::new_gb(4),
|
||||
SwapMode::SwapNoHibernate => BlkSize::new_gb(2),
|
||||
};
|
||||
self.generated_disk_parts = Some(
|
||||
PartLayout::gen_classic_layout(
|
||||
dev.clone(),
|
||||
BlkSize::new_mb(512),
|
||||
swap_size,
|
||||
)
|
||||
.part_list
|
||||
.iter()
|
||||
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
self.swap_mode = Some(sm);
|
||||
StageAction::None
|
||||
}
|
||||
@@ -206,23 +265,11 @@ impl PartitionStage {
|
||||
}
|
||||
|
||||
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||
let dev_parts_es: Option<Vec<(String, u16, Color)>> = if self.selected_disk_parts.is_some()
|
||||
{
|
||||
let p_l = self.gen_disk_layout_estimate();
|
||||
Some(
|
||||
p_l.part_list.iter()
|
||||
.map(|part_info| part_to_ct_params(part_info, p_l.dev.size))
|
||||
.collect()
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let dev_parts_text = match dev_parts_es {
|
||||
let dev_parts_text = match self.generated_disk_parts {
|
||||
Some(_) => widget::text(t!("partition.current_after_install")),
|
||||
None => widget::text(""),
|
||||
};
|
||||
let dev_parts_content = kira_color_bar::color_bar(dev_parts_es, 48);
|
||||
let dev_parts_content = kira_color_bar::color_bar(self.generated_disk_parts.clone(), 56);
|
||||
let dev_parts_content = widget::column![dev_parts_text, dev_parts_content];
|
||||
|
||||
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
||||
@@ -232,7 +279,7 @@ impl PartitionStage {
|
||||
Some(_) => widget::text(t!("partition.current_dev_layout")),
|
||||
None => widget::text(""),
|
||||
};
|
||||
let selected_dev_content = kira_color_bar::color_bar(self.selected_disk_parts.clone(), 48);
|
||||
let selected_dev_content = kira_color_bar::color_bar(self.selected_disk_parts.clone(), 56);
|
||||
let selected_dev_content = widget::column![selected_dev_text, selected_dev_content];
|
||||
|
||||
widget::column![
|
||||
|
||||
Reference in New Issue
Block a user