fix2222
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/dist/
|
||||||
|
/build/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,8 +4,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||||
from PySide6.QtCore import QObject, Signal, QThread # <--- 导入 QObject, Signal, QThread
|
from PySide6.QtCore import QObject, Signal, QThread
|
||||||
from system_info import SystemInfoManager # 确保 SystemInfoManager 已导入
|
from system_info import SystemInfoManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
# Use sed for robust removal with sudo
|
# Use sed for robust removal with sudo
|
||||||
# suppress_critical_dialog_on_stderr_match is added to handle cases where fstab might not exist
|
# suppress_critical_dialog_on_stderr_match is added to handle cases where fstab might not exist
|
||||||
# or sed reports no changes, which is not a critical error for removal.
|
# or sed reports no changes, which is not a critical error for removal.
|
||||||
command = ["sed", "-i", f"/UUID={uuid}/d", fstab_path]
|
command = ["sed", "-i", f"/{re.escape(uuid)}/d", fstab_path] # Use re.escape for UUID
|
||||||
success, _, stderr = self._execute_shell_command(
|
success, _, stderr = self._execute_shell_command(
|
||||||
command,
|
command,
|
||||||
f"从 {fstab_path} 中删除 UUID={uuid} 的条目失败",
|
f"从 {fstab_path} 中删除 UUID={uuid} 的条目失败",
|
||||||
@@ -163,6 +163,106 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
return True # Consider it a success if the entry is not there
|
return True # Consider it a success if the entry is not there
|
||||||
return False # Other errors are already handled by _execute_shell_command
|
return False # Other errors are already handled by _execute_shell_command
|
||||||
|
|
||||||
|
def _resolve_device_occupation(self, device_path, action_description="操作"):
|
||||||
|
"""
|
||||||
|
尝试解决设备占用问题。
|
||||||
|
检查:
|
||||||
|
1. 是否是交换分区,如果是则提示用户关闭。
|
||||||
|
2. 是否有进程占用,如果有则提示用户终止。
|
||||||
|
:param device_path: 要检查的设备路径。
|
||||||
|
:param action_description: 正在尝试的操作描述,用于用户提示。
|
||||||
|
:return: True 如果占用已解决或没有占用,False 如果用户取消或解决失败。
|
||||||
|
"""
|
||||||
|
logger.info(f"尝试解决设备 {device_path} 的占用问题,以便进行 {action_description}。")
|
||||||
|
|
||||||
|
# 1. 检查是否是交换分区
|
||||||
|
block_devices = self.system_manager.get_block_devices()
|
||||||
|
device_info = self.system_manager._find_device_by_path_recursive(block_devices, device_path)
|
||||||
|
|
||||||
|
# Check if it's a swap partition and currently active
|
||||||
|
if device_info and device_info.get('fstype') == 'swap' and device_info.get('mountpoint') == '[SWAP]':
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
None,
|
||||||
|
"设备占用 - 交换分区",
|
||||||
|
f"设备 {device_path} 是一个活跃的交换分区。为了进行 {action_description},需要关闭它。您要继续吗?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
logger.info(f"用户取消了关闭交换分区 {device_path}。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.info(f"尝试关闭交换分区 {device_path}。")
|
||||||
|
success, _, stderr = self._execute_shell_command(
|
||||||
|
["swapoff", device_path],
|
||||||
|
f"关闭交换分区 {device_path} 失败",
|
||||||
|
show_dialog=True # Show dialog for swapoff failure
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
logger.error(f"关闭交换分区 {device_path} 失败: {stderr}")
|
||||||
|
QMessageBox.critical(None, "错误", f"关闭交换分区 {device_path} 失败。无法进行 {action_description}。")
|
||||||
|
return False
|
||||||
|
QMessageBox.information(None, "信息", f"交换分区 {device_path} 已成功关闭。")
|
||||||
|
# After swapoff, it might still be reported by fuser, so continue to fuser check.
|
||||||
|
|
||||||
|
# 2. 检查是否有进程占用 (使用 fuser)
|
||||||
|
success_fuser, stdout_fuser, stderr_fuser = self._execute_shell_command(
|
||||||
|
["fuser", "-vm", device_path],
|
||||||
|
f"检查设备 {device_path} 占用失败",
|
||||||
|
show_dialog=False, # Don't show dialog if fuser fails (e.g., device not busy)
|
||||||
|
suppress_critical_dialog_on_stderr_match=(
|
||||||
|
"No such file or directory", # if device doesn't exist
|
||||||
|
"not found", # if fuser itself isn't found
|
||||||
|
"Usage:" # if fuser gets invalid args, though unlikely here
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pids = []
|
||||||
|
process_info_lines = []
|
||||||
|
# Parse fuser output only if it was successful and has stdout
|
||||||
|
if success_fuser and stdout_fuser:
|
||||||
|
for line in stdout_fuser.splitlines():
|
||||||
|
# Example: "/dev/sdb1: root 12078 .rce. gpg-agent"
|
||||||
|
match = re.match(r'^\S+:\s+\S+\s+(\d+)\s+.*', line)
|
||||||
|
if match:
|
||||||
|
pid = match.group(1)
|
||||||
|
pids.append(pid)
|
||||||
|
process_info_lines.append(line.strip())
|
||||||
|
|
||||||
|
if pids:
|
||||||
|
process_list_str = "\n".join(process_info_lines)
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
None,
|
||||||
|
"设备占用 - 进程",
|
||||||
|
f"设备 {device_path} 正在被以下进程占用:\n{process_list_str}\n\n您要强制终止这些进程吗?这可能会导致数据丢失或系统不稳定!",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
logger.info(f"用户取消了终止占用设备 {device_path} 的进程。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.info(f"尝试终止占用设备 {device_path} 的进程: {', '.join(pids)}。")
|
||||||
|
all_killed = True
|
||||||
|
for pid in pids:
|
||||||
|
kill_success, _, kill_stderr = self._execute_shell_command(
|
||||||
|
["kill", "-9", pid],
|
||||||
|
f"终止进程 {pid} 失败",
|
||||||
|
show_dialog=True # Show dialog for kill failure
|
||||||
|
)
|
||||||
|
if not kill_success:
|
||||||
|
logger.error(f"终止进程 {pid} 失败: {kill_stderr}")
|
||||||
|
all_killed = False
|
||||||
|
if not all_killed:
|
||||||
|
QMessageBox.critical(None, "错误", f"未能终止所有占用设备 {device_path} 的进程。无法进行 {action_description}。")
|
||||||
|
return False
|
||||||
|
QMessageBox.information(None, "信息", f"已尝试终止占用设备 {device_path} 的所有进程。")
|
||||||
|
else:
|
||||||
|
logger.info(f"设备 {device_path} 未被任何进程占用。")
|
||||||
|
|
||||||
|
logger.info(f"设备 {device_path} 的占用问题已尝试解决。")
|
||||||
|
return True
|
||||||
|
|
||||||
def mount_partition(self, device_path, mount_point, add_to_fstab=False):
|
def mount_partition(self, device_path, mount_point, add_to_fstab=False):
|
||||||
"""
|
"""
|
||||||
挂载指定设备到指定挂载点。
|
挂载指定设备到指定挂载点。
|
||||||
@@ -214,34 +314,57 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
"""
|
"""
|
||||||
logger.info(f"尝试卸载设备 {device_path}。")
|
logger.info(f"尝试卸载设备 {device_path}。")
|
||||||
# 定义表示设备已未挂载的错误信息(中英文)
|
# 定义表示设备已未挂载的错误信息(中英文)
|
||||||
# 增加了 "未指定挂载点"
|
|
||||||
already_unmounted_errors = ("not mounted", "未挂载", "未指定挂载点")
|
already_unmounted_errors = ("not mounted", "未挂载", "未指定挂载点")
|
||||||
|
device_busy_errors = ("device is busy", "设备或资源忙", "Device or resource busy") # Common "device busy" messages
|
||||||
|
|
||||||
# 调用 _execute_shell_command,并告诉它在遇到“已未挂载”错误时,不要弹出其自身的关键错误对话框。
|
# First attempt to unmount
|
||||||
success, _, stderr = self._execute_shell_command(
|
success, _, stderr = self._execute_shell_command(
|
||||||
["umount", device_path],
|
["umount", device_path],
|
||||||
f"卸载设备 {device_path} 失败",
|
f"卸载设备 {device_path} 失败",
|
||||||
suppress_critical_dialog_on_stderr_match=already_unmounted_errors,
|
suppress_critical_dialog_on_stderr_match=already_unmounted_errors + device_busy_errors, # Suppress both types of errors for initial attempt
|
||||||
show_dialog=show_dialog_on_error # <--- 将 show_dialog_on_error 传递给底层命令执行器
|
show_dialog=False # Don't show dialog on first attempt, we'll handle it
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# 如果命令成功执行,则卸载成功。
|
|
||||||
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功卸载。")
|
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功卸载。")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 如果命令失败,检查是否是因为设备已经未挂载或挂载点未指定。
|
# Check if it failed because it was already unmounted
|
||||||
is_already_unmounted_error = any(s in stderr for s in already_unmounted_errors)
|
if any(s in stderr for s in already_unmounted_errors):
|
||||||
|
|
||||||
if is_already_unmounted_error:
|
|
||||||
logger.info(f"设备 {device_path} 已经处于未挂载状态(或挂载点未指定),无需重复卸载。")
|
logger.info(f"设备 {device_path} 已经处于未挂载状态(或挂载点未指定),无需重复卸载。")
|
||||||
# 这种情况下,操作结果符合预期(设备已是未挂载),不弹出对话框,并返回 True。
|
return True # Consider it a success
|
||||||
|
|
||||||
|
# Check if it failed because the device was busy
|
||||||
|
if any(s in stderr for s in device_busy_errors):
|
||||||
|
logger.warning(f"卸载设备 {device_path} 失败,设备忙。尝试解决占用问题。")
|
||||||
|
# Call the new helper to resolve occupation
|
||||||
|
if self._resolve_device_occupation(device_path, action_description=f"卸载 {device_path}"):
|
||||||
|
logger.info(f"设备 {device_path} 占用问题已解决,重试卸载。")
|
||||||
|
# Retry unmount after resolving occupation
|
||||||
|
retry_success, _, retry_stderr = self._execute_shell_command(
|
||||||
|
["umount", device_path],
|
||||||
|
f"重试卸载设备 {device_path} 失败",
|
||||||
|
suppress_critical_dialog_on_stderr_match=already_unmounted_errors,
|
||||||
|
show_dialog=show_dialog_on_error # Show dialog if retry fails for other reasons
|
||||||
|
)
|
||||||
|
if retry_success:
|
||||||
|
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功卸载。")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 对于其他类型的卸载失败(例如设备忙、权限不足等),
|
logger.error(f"重试卸载设备 {device_path} 失败: {retry_stderr}")
|
||||||
# _execute_shell_command 应该已经弹出了关键错误对话框
|
if show_dialog_on_error and not any(s in retry_stderr for s in already_unmounted_errors):
|
||||||
# (因为它没有匹配到 `already_unmounted_errors` 进行抑制)。
|
QMessageBox.critical(None, "错误", f"重试卸载设备 {device_path} 失败。\n错误详情: {retry_stderr}")
|
||||||
# 所以,这里我们不需要再次弹出对话框,直接返回 False。
|
return False
|
||||||
|
else:
|
||||||
|
logger.info(f"用户取消或未能解决设备 {device_path} 的占用问题。")
|
||||||
|
if show_dialog_on_error:
|
||||||
|
QMessageBox.critical(None, "错误", f"未能解决设备 {device_path} 的占用问题,无法卸载。")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Other types of unmount failures
|
||||||
|
logger.error(f"卸载设备 {device_path} 失败: {stderr}")
|
||||||
|
if show_dialog_on_error:
|
||||||
|
QMessageBox.critical(None, "错误", f"卸载设备 {device_path} 失败。\n错误详情: {stderr}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_disk_free_space_info_mib(self, disk_path, total_disk_mib):
|
def get_disk_free_space_info_mib(self, disk_path, total_disk_mib):
|
||||||
@@ -317,6 +440,11 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
QMessageBox.critical(None, "错误", "无效的磁盘路径或分区表类型。")
|
QMessageBox.critical(None, "错误", "无效的磁盘路径或分区表类型。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# NEW: 尝试解决磁盘占用问题
|
||||||
|
if not self._resolve_device_occupation(disk_path, action_description=f"在 {disk_path} 上创建分区"):
|
||||||
|
logger.info(f"用户取消或未能解决磁盘 {disk_path} 的占用问题,取消创建分区。")
|
||||||
|
return False
|
||||||
|
|
||||||
# 1. 检查磁盘是否有分区表
|
# 1. 检查磁盘是否有分区表
|
||||||
has_partition_table = False
|
has_partition_table = False
|
||||||
# Use suppress_critical_dialog_on_stderr_match for "unrecognized disk label" when checking
|
# Use suppress_critical_dialog_on_stderr_match for "unrecognized disk label" when checking
|
||||||
@@ -419,6 +547,11 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
:param device_path: 要删除的分区路径。
|
:param device_path: 要删除的分区路径。
|
||||||
:return: True 如果成功,否则 False。
|
:return: True 如果成功,否则 False。
|
||||||
"""
|
"""
|
||||||
|
# NEW: 尝试解决分区占用问题
|
||||||
|
if not self._resolve_device_occupation(device_path, action_description=f"删除分区 {device_path}"):
|
||||||
|
logger.info(f"用户取消或未能解决分区 {device_path} 的占用问题,取消删除分区。")
|
||||||
|
return False
|
||||||
|
|
||||||
reply = QMessageBox.question(None, "确认删除分区",
|
reply = QMessageBox.question(None, "确认删除分区",
|
||||||
f"您确定要删除分区 {device_path} 吗?此操作将擦除分区上的所有数据!",
|
f"您确定要删除分区 {device_path} 吗?此操作将擦除分区上的所有数据!",
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
@@ -426,10 +559,8 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
logger.info(f"用户取消了删除分区 {device_path} 的操作。")
|
logger.info(f"用户取消了删除分区 {device_path} 的操作。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 尝试卸载分区
|
# 尝试卸载分区 (此方法内部会尝试解决占用问题)
|
||||||
# The unmount_partition method now handles "not mounted" gracefully without dialog and returns True.
|
self.unmount_partition(device_path, show_dialog_on_error=False)
|
||||||
# So, we just call it.
|
|
||||||
self.unmount_partition(device_path, show_dialog_on_error=False) # show_dialog_on_error=False means no dialog for other errors too.
|
|
||||||
|
|
||||||
# 从 fstab 中移除条目
|
# 从 fstab 中移除条目
|
||||||
# _remove_fstab_entry also handles "not found" gracefully.
|
# _remove_fstab_entry also handles "not found" gracefully.
|
||||||
@@ -471,6 +602,11 @@ class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
|||||||
logger.info("用户取消了文件系统选择。")
|
logger.info("用户取消了文件系统选择。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# NEW: 尝试解决设备占用问题
|
||||||
|
if not self._resolve_device_occupation(device_path, action_description=f"格式化设备 {device_path}"):
|
||||||
|
logger.info(f"用户取消或未能解决设备 {device_path} 的占用问题,取消格式化。")
|
||||||
|
return False
|
||||||
|
|
||||||
reply = QMessageBox.question(None, "确认格式化",
|
reply = QMessageBox.question(None, "确认格式化",
|
||||||
f"您确定要格式化设备 {device_path} 为 {fstype} 吗?此操作将擦除所有数据!",
|
f"您确定要格式化设备 {device_path} 为 {fstype} 吗?此操作将擦除所有数据!",
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from PySide6.QtWidgets import QMessageBox
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class LvmOperations:
|
class LvmOperations:
|
||||||
def __init__(self):
|
def __init__(self, disk_ops=None): # <--- Add disk_ops parameter
|
||||||
pass
|
self.disk_ops = disk_ops # Store disk_ops instance
|
||||||
|
|
||||||
def _execute_shell_command(self, command_list, error_message, root_privilege=True,
|
def _execute_shell_command(self, command_list, error_message, root_privilege=True,
|
||||||
suppress_critical_dialog_on_stderr_match=None, input_data=None,
|
suppress_critical_dialog_on_stderr_match=None, input_data=None,
|
||||||
@@ -92,6 +92,15 @@ class LvmOperations:
|
|||||||
QMessageBox.critical(None, "错误", "无效的设备路径。")
|
QMessageBox.critical(None, "错误", "无效的设备路径。")
|
||||||
return False
|
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, "确认创建物理卷",
|
reply = QMessageBox.question(None, "确认创建物理卷",
|
||||||
f"您确定要在设备 {device_path} 上创建物理卷吗?此操作将覆盖设备上的数据。",
|
f"您确定要在设备 {device_path} 上创建物理卷吗?此操作将覆盖设备上的数据。",
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
|
|||||||
@@ -36,11 +36,12 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 初始化管理器和操作类
|
# 初始化管理器和操作类
|
||||||
self.system_manager = SystemInfoManager()
|
self.system_manager = SystemInfoManager()
|
||||||
self.lvm_ops = LvmOperations() # <--- 先初始化 LvmOperations
|
# Correct order for dependency injection:
|
||||||
# <--- 将 lvm_ops 实例传递给 DiskOperations
|
self.lvm_ops = LvmOperations() # Initialize LVM first
|
||||||
self.disk_ops = DiskOperations(self.system_manager, self.lvm_ops)
|
self.disk_ops = DiskOperations(self.system_manager, self.lvm_ops) # DiskOperations needs system_manager and lvm_ops
|
||||||
self.raid_ops = RaidOperations()
|
self.lvm_ops.disk_ops = self.disk_ops # Inject disk_ops into lvm_ops
|
||||||
# self.lvm_ops = LvmOperations() # 这一行是重复的,可以删除
|
self.raid_ops = RaidOperations(self.system_manager, self.disk_ops) # RaidOperations needs system_manager and disk_ops
|
||||||
|
|
||||||
|
|
||||||
# 连接刷新按钮的信号到槽函数
|
# 连接刷新按钮的信号到槽函数
|
||||||
if hasattr(self.ui, 'refreshButton'):
|
if hasattr(self.ui, 'refreshButton'):
|
||||||
@@ -188,6 +189,11 @@ class MainWindow(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
处理擦除物理盘分区表的操作。
|
处理擦除物理盘分区表的操作。
|
||||||
"""
|
"""
|
||||||
|
# NEW: 尝试解决磁盘占用问题
|
||||||
|
if not self.disk_ops._resolve_device_occupation(device_path, action_description=f"擦除 {device_path} 上的分区表"):
|
||||||
|
logger.info(f"用户取消或未能解决磁盘 {device_path} 的占用问题,取消擦除分区表。")
|
||||||
|
return
|
||||||
|
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"确认擦除分区表",
|
"确认擦除分区表",
|
||||||
@@ -455,7 +461,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
array_uuid = array_data.get('uuid')
|
array_uuid = array_data.get('uuid')
|
||||||
if not array_uuid or array_uuid == 'N/A':
|
if not array_uuid or array_uuid == 'N/A':
|
||||||
QMessageBox.critical(self, "错误", f"无法激活 RAID 阵列 {array_path}:缺少 UUID 信息。")
|
QMessageBox.critical(None, "错误", f"无法激活 RAID 阵列 {array_path}:缺少 UUID 信息。")
|
||||||
logger.error(f"激活 RAID 阵列 {array_path} 失败:缺少 UUID。")
|
logger.error(f"激活 RAID 阵列 {array_path} 失败:缺少 UUID。")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -494,7 +500,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _handle_delete_active_raid_array(self, array_path, member_devices, uuid): # <--- 修改方法名并添加 uuid 参数
|
def _handle_delete_active_raid_array(self, array_path, member_devices, uuid): # <--- 修改方法名并添加 uuid 参数
|
||||||
"""
|
"""
|
||||||
处理删除活动 RAID 阵列的操作。
|
处理删除一个活动的 RAID 阵列的操作。
|
||||||
"""
|
"""
|
||||||
if self.raid_ops.delete_active_raid_array(array_path, member_devices, uuid): # <--- 调用修改后的方法
|
if self.raid_ops.delete_active_raid_array(array_path, member_devices, uuid): # <--- 调用修改后的方法
|
||||||
self.refresh_all_info()
|
self.refresh_all_info()
|
||||||
@@ -850,4 +856,3 @@ if __name__ == "__main__":
|
|||||||
widget = MainWindow()
|
widget = MainWindow()
|
||||||
widget.show()
|
widget.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ from system_info import SystemInfoManager
|
|||||||
import re
|
import re
|
||||||
import pexpect # <--- 新增导入 pexpect
|
import pexpect # <--- 新增导入 pexpect
|
||||||
import sys
|
import sys
|
||||||
|
# NEW: 导入 DiskOperations
|
||||||
|
from disk_operations import DiskOperations
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class RaidOperations:
|
class RaidOperations:
|
||||||
def __init__(self):
|
def __init__(self, system_manager: SystemInfoManager, disk_ops: DiskOperations): # <--- Add system_manager and disk_ops parameters
|
||||||
self.system_manager = SystemInfoManager()
|
self.system_manager = system_manager
|
||||||
|
self.disk_ops = disk_ops # Store disk_ops instance
|
||||||
|
|
||||||
def _execute_shell_command(self, command_list, error_msg_prefix, suppress_critical_dialog_on_stderr_match=None, input_to_command=None):
|
def _execute_shell_command(self, command_list, error_msg_prefix, suppress_critical_dialog_on_stderr_match=None, input_to_command=None):
|
||||||
"""
|
"""
|
||||||
@@ -165,7 +168,7 @@ class RaidOperations:
|
|||||||
|
|
||||||
# 等待一小段时间,确保 mdadm 接收并处理了输入,并清空缓冲区
|
# 等待一小段时间,确保 mdadm 接收并处理了输入,并清空缓冲区
|
||||||
# 避免在下一次 expect 之前,旧的输出再次被匹配
|
# 避免在下一次 expect 之前,旧的输出再次被匹配
|
||||||
child.expect(pexpect.TIMEOUT, timeout=1)
|
# child.expect(pexpect.TIMEOUT, timeout=1) # This line might consume output needed for next prompt
|
||||||
|
|
||||||
except pexpect.exceptions.TIMEOUT:
|
except pexpect.exceptions.TIMEOUT:
|
||||||
# 如果在等待任何提示时超时,说明没有更多提示了,或者命令已完成。
|
# 如果在等待任何提示时超时,说明没有更多提示了,或者命令已完成。
|
||||||
@@ -179,7 +182,6 @@ class RaidOperations:
|
|||||||
break # 跳出 while 循环,进入等待 EOF 阶段
|
break # 跳出 while 循环,进入等待 EOF 阶段
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 增加一个合理的超时时间,以防 mdadm 在后台进行长时间初始化
|
# 增加一个合理的超时时间,以防 mdadm 在后台进行长时间初始化
|
||||||
child.expect(pexpect.EOF, timeout=120) # 例如,等待 120 秒
|
child.expect(pexpect.EOF, timeout=120) # 例如,等待 120 秒
|
||||||
@@ -277,6 +279,11 @@ class RaidOperations:
|
|||||||
QMessageBox.critical(None, "错误", "RAID10 至少需要两个设备。")
|
QMessageBox.critical(None, "错误", "RAID10 至少需要两个设备。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# NEW: 尝试解决所有成员设备的占用问题
|
||||||
|
for dev in devices:
|
||||||
|
if not self.disk_ops._resolve_device_occupation(dev, action_description=f"创建 RAID 阵列,成员 {dev}"):
|
||||||
|
logger.info(f"用户取消或未能解决设备 {dev} 的占用问题,取消创建 RAID 阵列。")
|
||||||
|
return False
|
||||||
|
|
||||||
# 确认操作
|
# 确认操作
|
||||||
reply = QMessageBox.question(None, "确认创建 RAID 阵列",
|
reply = QMessageBox.question(None, "确认创建 RAID 阵列",
|
||||||
@@ -358,6 +365,7 @@ class RaidOperations:
|
|||||||
|
|
||||||
# 这里需要确保 /etc/mdadm.conf 存在且可写入
|
# 这里需要确保 /etc/mdadm.conf 存在且可写入
|
||||||
# _execute_shell_command 会自动添加 sudo
|
# _execute_shell_command 会自动添加 sudo
|
||||||
|
# 使用 tee -a 而不是 >> 来确保 sudo 权限下的写入
|
||||||
success_append, _, _ = self._execute_shell_command(
|
success_append, _, _ = self._execute_shell_command(
|
||||||
["bash", "-c", f"echo '{scan_stdout.strip()}' | tee -a /etc/mdadm.conf > /dev/null"],
|
["bash", "-c", f"echo '{scan_stdout.strip()}' | tee -a /etc/mdadm.conf > /dev/null"],
|
||||||
"更新 /etc/mdadm/mdadm.conf 失败"
|
"更新 /etc/mdadm/mdadm.conf 失败"
|
||||||
@@ -387,13 +395,11 @@ class RaidOperations:
|
|||||||
|
|
||||||
logger.info(f"尝试停止 RAID 阵列: {array_path}")
|
logger.info(f"尝试停止 RAID 阵列: {array_path}")
|
||||||
|
|
||||||
# 尝试卸载阵列(如果已挂载),不显示错误对话框
|
# NEW: 尝试卸载阵列(如果已挂载),利用 DiskOperations 的增强卸载功能
|
||||||
# _execute_shell_command 会自动添加 sudo
|
# unmount_partition 内部会尝试解决设备占用问题
|
||||||
self._execute_shell_command(
|
if not self.disk_ops.unmount_partition(array_path, show_dialog_on_error=False):
|
||||||
["umount", array_path],
|
logger.warning(f"未能成功卸载 RAID 阵列 {array_path}。尝试继续停止操作。")
|
||||||
f"尝试卸载 {array_path} 失败",
|
# 即使卸载失败,我们仍然尝试停止 mdadm 阵列,因为有时 mdadm --stop 可以在设备忙时强制停止。
|
||||||
suppress_critical_dialog_on_stderr_match=("not mounted", "未挂载") # 修改这里,添加中文错误信息
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self._execute_shell_command(["mdadm", "--stop", array_path], f"停止 RAID 阵列 {array_path} 失败")[0]:
|
if not self._execute_shell_command(["mdadm", "--stop", array_path], f"停止 RAID 阵列 {array_path} 失败")[0]:
|
||||||
return False
|
return False
|
||||||
@@ -421,6 +427,7 @@ class RaidOperations:
|
|||||||
logger.info(f"尝试删除活动 RAID 阵列: {array_path} (UUID: {uuid})")
|
logger.info(f"尝试删除活动 RAID 阵列: {array_path} (UUID: {uuid})")
|
||||||
|
|
||||||
# 1. 停止阵列
|
# 1. 停止阵列
|
||||||
|
# stop_raid_array 内部会调用 self.disk_ops.unmount_partition 来尝试卸载并解决占用
|
||||||
if not self.stop_raid_array(array_path):
|
if not self.stop_raid_array(array_path):
|
||||||
QMessageBox.critical(None, "错误", f"删除 RAID 阵列 {array_path} 失败,因为无法停止阵列。")
|
QMessageBox.critical(None, "错误", f"删除 RAID 阵列 {array_path} 失败,因为无法停止阵列。")
|
||||||
return False
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user