添加解除占用功能

This commit is contained in:
zj
2026-02-05 01:41:11 +08:00
parent 6c4257532b
commit c7e0dc1036
10 changed files with 512 additions and 423 deletions

View File

@@ -6,12 +6,12 @@ from PySide6.QtWidgets import QMessageBox
logger = logging.getLogger(__name__)
class LvmOperations:
def __init__(self, disk_ops=None): # <--- Add disk_ops parameter
self.disk_ops = disk_ops # Store disk_ops instance
def __init__(self):
pass
def _execute_shell_command(self, command_list, error_message, root_privilege=True,
suppress_critical_dialog_on_stderr_match=None, input_data=None,
show_dialog=True): # <--- 新增 show_dialog 参数,默认为 True
suppress_critical_dialog_on_stderr_match=None, input_data=None,
show_dialog=True, expected_non_zero_exit_codes=None):
"""
通用地运行一个 shell 命令,并处理错误。
:param command_list: 命令及其参数的列表。
@@ -21,11 +21,12 @@ class LvmOperations:
可以是字符串或字符串元组/列表。
:param input_data: 传递给命令stdin的数据 (str)。
:param show_dialog: 如果为 False则不显示关键错误对话框。
:param expected_non_zero_exit_codes: 预期为非零但仍视为成功的退出码列表。
:return: (True/False, stdout_str, stderr_str)
"""
if not all(isinstance(arg, str) for arg in command_list):
logger.error(f"命令列表包含非字符串元素: {command_list}")
if show_dialog: # <--- 根据 show_dialog 决定是否弹出对话框
if show_dialog:
QMessageBox.critical(None, "错误", f"内部错误:尝试执行的命令包含无效参数。\n命令详情: {command_list}")
return False, "", "内部错误:命令参数类型不正确。"
@@ -53,6 +54,12 @@ class LvmOperations:
logger.error(f"标准输出: {e.stdout.strip()}")
logger.error(f"标准错误: {stderr_output}")
# NEW LOGIC: Check if the exit code is expected as non-critical
if expected_non_zero_exit_codes and e.returncode in expected_non_zero_exit_codes:
logger.info(f"命令 '{full_cmd_str}' 以预期非零退出码 {e.returncode} 结束,仍视为成功。")
return True, e.stdout.strip(), stderr_output # Treat as success
should_suppress_dialog = False
if suppress_critical_dialog_on_stderr_match:
if isinstance(suppress_critical_dialog_on_stderr_match, str):
@@ -64,23 +71,21 @@ class LvmOperations:
should_suppress_dialog = True
break
if show_dialog and not should_suppress_dialog: # <--- 根据 show_dialog 决定是否弹出对话框
if show_dialog and not should_suppress_dialog:
QMessageBox.critical(None, "错误", f"{error_message}\n错误详情: {stderr_output}")
return False, e.stdout.strip(), stderr_output
except FileNotFoundError:
if show_dialog: # <--- 根据 show_dialog 决定是否弹出对话框
if show_dialog:
QMessageBox.critical(None, "错误", f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
logger.error(f"命令 '{command_list[0]}' 未找到。")
return False, "", f"命令 '{command_list[0]}' 未找到。"
except Exception as e:
if show_dialog: # <--- 根据 show_dialog 决定是否弹出对话框
if show_dialog:
QMessageBox.critical(None, "错误", f"执行命令时发生未知错误: {e}")
logger.error(f"执行命令 {full_cmd_str} 时发生未知错误: {e}")
return False, "", str(e)
# 以下是 LvmOperations 中其他方法的代码,保持不变。
# ... (create_pv, delete_pv, create_vg, delete_vg, create_lv, delete_lv, activate_lv, deactivate_lv) ...
def create_pv(self, device_path):
"""
创建物理卷 (PV)。
@@ -92,15 +97,6 @@ class LvmOperations:
QMessageBox.critical(None, "错误", "无效的设备路径。")
return False
# NEW: 尝试解决设备占用问题
if self.disk_ops: # Ensure disk_ops is available
if not self.disk_ops._resolve_device_occupation(device_path, action_description=f"{device_path} 上创建物理卷"):
logger.info(f"用户取消或未能解决设备 {device_path} 的占用问题,取消创建物理卷。")
return False
else:
logger.warning("DiskOperations 实例未传递给 LvmOperations无法解决设备占用问题。")
reply = QMessageBox.question(None, "确认创建物理卷",
f"您确定要在设备 {device_path} 上创建物理卷吗?此操作将覆盖设备上的数据。",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
@@ -109,20 +105,17 @@ class LvmOperations:
return False
logger.info(f"尝试在 {device_path} 上创建物理卷。")
# 第一次尝试创建物理卷,抑制 "device is partitioned" 错误,因为我们将在代码中处理它
success, _, stderr = self._execute_shell_command(
["pvcreate", "-y", device_path], # -y 自动确认
["pvcreate", "-y", device_path],
f"{device_path} 上创建物理卷失败",
suppress_critical_dialog_on_stderr_match="device is partitioned" # 抑制此特定错误
suppress_critical_dialog_on_stderr_match="device is partitioned"
)
if success:
QMessageBox.information(None, "成功", f"物理卷已在 {device_path} 上成功创建。")
return True
else:
# 如果 pvcreate 失败,检查是否是 "device is partitioned" 错误
if "device is partitioned" in stderr:
# 提示用户是否要擦除分区表
wipe_reply = QMessageBox.question(
None, "设备已分区",
f"设备 {device_path} 已分区。您是否要擦除设备上的所有分区表,"
@@ -131,35 +124,28 @@ class LvmOperations:
)
if wipe_reply == QMessageBox.Yes:
logger.warning(f"用户选择擦除 {device_path} 上的分区表并创建物理卷。")
# 尝试擦除分区表 (使用 GPT 作为默认,通常更现代且支持大容量磁盘)
# 注意:这里需要 parted 命令,确保系统已安装 parted
mklabel_success, _, mklabel_stderr = self._execute_shell_command(
["parted", "-s", device_path, "mklabel", "gpt"], # 使用 gpt 分区表
["parted", "-s", device_path, "mklabel", "gpt"],
f"擦除 {device_path} 上的分区表失败"
)
if mklabel_success:
logger.info(f"已成功擦除 {device_path} 上的分区表。重试创建物理卷。")
# 再次尝试创建物理卷
retry_success, _, retry_stderr = self._execute_shell_command(
["pvcreate", "-y", device_path],
f"{device_path} 上创建物理卷失败 (重试)"
# 重试时不再抑制如果再次失败_execute_shell_command 会显示错误
)
if retry_success:
QMessageBox.information(None, "成功", f"物理卷已在 {device_path} 上成功创建。")
return True
else:
# 如果重试失败_execute_shell_command 已经显示错误
return False
else:
# mklabel 失败_execute_shell_command 已经显示错误
return False
else:
logger.info(f"用户取消了擦除 {device_path} 分区表的操作。")
QMessageBox.information(None, "信息", f"未在已分区设备 {device_path} 上创建物理卷。")
return False
else:
# 对于其他类型的 pvcreate 失败_execute_shell_command 已经显示了错误
return False
def delete_pv(self, device_path):
@@ -374,4 +360,3 @@ class LvmOperations:
return True
else:
return False