fix 33
This commit is contained in:
@@ -1,80 +1,78 @@
|
||||
# disk_operations.py
|
||||
import subprocess
|
||||
import logging
|
||||
import re
|
||||
import os
|
||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||
from system_info import SystemInfoManager
|
||||
from PySide6.QtCore import QObject, Signal, QThread # <--- 导入 QObject, Signal, QThread
|
||||
from system_info import SystemInfoManager # 确保 SystemInfoManager 已导入
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DiskOperations:
|
||||
def __init__(self, system_manager: SystemInfoManager): # NEW: 接收 system_manager 实例
|
||||
self.system_manager = system_manager
|
||||
# --- 新增: 后台格式化工作线程 ---
|
||||
class FormatWorker(QObject):
|
||||
# 定义信号,用于向主线程发送格式化结果
|
||||
finished = Signal(bool, str, str, str) # 成功状态, 设备路径, 标准输出, 标准错误
|
||||
started = Signal(str) # 设备路径
|
||||
|
||||
def __init__(self, device_path, fs_type, execute_shell_command_func, parent=None):
|
||||
super().__init__(parent)
|
||||
self.device_path = device_path
|
||||
self.fs_type = fs_type
|
||||
# 接收一个可调用的函数,用于执行shell命令 (这里是 DiskOperations._execute_shell_command)
|
||||
self._execute_shell_command_func = execute_shell_command_func
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
在单独线程中执行格式化命令。
|
||||
"""
|
||||
self.started.emit(self.device_path)
|
||||
logger.info(f"后台格式化开始: 设备 {self.device_path}, 文件系统 {self.fs_type}")
|
||||
|
||||
command_list = []
|
||||
if self.fs_type == "ext4":
|
||||
command_list = ["mkfs.ext4", "-F", self.device_path] # -F 强制执行
|
||||
elif self.fs_type == "xfs":
|
||||
command_list = ["mkfs.xfs", "-f", self.device_path] # -f 强制执行
|
||||
elif self.fs_type == "ntfs":
|
||||
command_list = ["mkfs.ntfs", "-f", self.device_path] # -f 强制执行
|
||||
elif self.fs_type == "fat32":
|
||||
command_list = ["mkfs.vfat", "-F", "32", self.device_path] # -F 32 指定FAT32
|
||||
else:
|
||||
logger.error(f"不支持的文件系统类型: {self.fs_type}")
|
||||
self.finished.emit(False, self.device_path, "", f"不支持的文件系统类型: {self.fs_type}")
|
||||
return
|
||||
|
||||
# 调用传入的shell命令执行函数,并禁用其内部的QMessageBox显示
|
||||
success, stdout, stderr = self._execute_shell_command_func(
|
||||
command_list,
|
||||
f"格式化设备 {self.device_path} 为 {self.fs_type} 失败",
|
||||
root_privilege=True,
|
||||
show_dialog=False # <--- 关键:阻止工作线程弹出 QMessageBox
|
||||
)
|
||||
self.finished.emit(success, self.device_path, stdout, stderr)
|
||||
logger.info(f"后台格式化完成: 设备 {self.device_path}, 成功: {success}")
|
||||
|
||||
|
||||
class DiskOperations(QObject): # <--- 继承 QObject 以便发出信号
|
||||
# 定义信号,用于通知主线程格式化操作的开始和结束
|
||||
formatting_finished = Signal(bool, str, str, str) # 成功状态, 设备路径, 标准输出, 标准错误
|
||||
formatting_started = Signal(str) # 设备路径
|
||||
|
||||
def __init__(self, system_manager: SystemInfoManager, lvm_ops, parent=None): # <--- 构造函数现在接收 lvm_ops 实例
|
||||
super().__init__(parent)
|
||||
self.system_manager = system_manager
|
||||
self._lvm_ops = lvm_ops # 保存 LvmOperations 实例,以便调用其 _execute_shell_command
|
||||
self.active_format_workers = {} # 用于跟踪正在运行的格式化线程
|
||||
|
||||
# DiskOperations 内部的 _execute_shell_command 只是对 LvmOperations._execute_shell_command 的包装
|
||||
# 这样所有 shell 命令都通过 LvmOperations 的统一入口,可以控制 show_dialog
|
||||
def _execute_shell_command(self, command_list, error_message, root_privilege=True,
|
||||
suppress_critical_dialog_on_stderr_match=None, input_data=None):
|
||||
"""
|
||||
通用地运行一个 shell 命令,并处理错误。
|
||||
:param command_list: 命令及其参数的列表。
|
||||
:param error_message: 命令失败时显示给用户的错误消息。
|
||||
:param root_privilege: 如果为 True,则使用 sudo 执行命令。
|
||||
:param suppress_critical_dialog_on_stderr_match: 如果 stderr 包含此字符串,则不显示关键错误对话框。
|
||||
可以是字符串或字符串元组。
|
||||
:param input_data: 传递给命令stdin的数据 (str)。
|
||||
:return: (True/False, stdout_str, stderr_str)
|
||||
"""
|
||||
if not all(isinstance(arg, str) for arg in command_list):
|
||||
logger.error(f"命令列表包含非字符串元素: {command_list}")
|
||||
QMessageBox.critical(None, "错误", f"内部错误:尝试执行的命令包含无效参数。\n命令详情: {command_list}")
|
||||
return False, "", "内部错误:命令参数类型不正确。"
|
||||
|
||||
if root_privilege:
|
||||
command_list = ["sudo"] + command_list
|
||||
|
||||
full_cmd_str = ' '.join(command_list)
|
||||
logger.debug(f"执行命令: {full_cmd_str}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command_list,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
encoding='utf-8',
|
||||
input=input_data
|
||||
)
|
||||
logger.info(f"命令成功: {full_cmd_str}")
|
||||
return True, result.stdout.strip(), result.stderr.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
stderr_output = e.stderr.strip()
|
||||
logger.error(f"命令失败: {full_cmd_str}")
|
||||
logger.error(f"退出码: {e.returncode}")
|
||||
logger.error(f"标准输出: {e.stdout.strip()}")
|
||||
logger.error(f"标准错误: {stderr_output}")
|
||||
|
||||
# Determine if the error dialog should be suppressed by this function
|
||||
should_suppress_dialog_here = False
|
||||
if suppress_critical_dialog_on_stderr_match:
|
||||
if isinstance(suppress_critical_dialog_on_stderr_match, str):
|
||||
if suppress_critical_dialog_on_stderr_match in stderr_output:
|
||||
should_suppress_dialog_here = True
|
||||
elif isinstance(suppress_critical_dialog_on_stderr_match, tuple):
|
||||
if any(s in stderr_output for s in suppress_critical_dialog_on_stderr_match):
|
||||
should_suppress_dialog_here = True
|
||||
|
||||
if should_suppress_dialog_here:
|
||||
logger.info(f"错误信息 '{stderr_output}' 匹配抑制条件,不显示关键错误对话框。")
|
||||
else:
|
||||
QMessageBox.critical(None, "错误", f"{error_message}\n错误详情: {stderr_output}")
|
||||
return False, e.stdout.strip(), stderr_output
|
||||
except FileNotFoundError:
|
||||
QMessageBox.critical(None, "错误", f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
||||
logger.error(f"命令 '{command_list[0]}' 未找到。")
|
||||
return False, "", f"命令 '{command_list[0]}' 未找到。"
|
||||
except Exception as e:
|
||||
QMessageBox.critical(None, "错误", f"执行命令时发生未知错误: {e}")
|
||||
logger.error(f"执行命令 {full_cmd_str} 时发生未知错误: {e}")
|
||||
return False, "", str(e)
|
||||
suppress_critical_dialog_on_stderr_match=None, input_data=None,
|
||||
show_dialog=True):
|
||||
return self._lvm_ops._execute_shell_command(
|
||||
command_list, error_message, root_privilege, suppress_critical_dialog_on_stderr_match, input_data, show_dialog
|
||||
)
|
||||
|
||||
def _add_to_fstab(self, device_path, mount_point, fstype, uuid):
|
||||
"""
|
||||
@@ -223,7 +221,8 @@ class DiskOperations:
|
||||
success, _, stderr = self._execute_shell_command(
|
||||
["umount", device_path],
|
||||
f"卸载设备 {device_path} 失败",
|
||||
suppress_critical_dialog_on_stderr_match=already_unmounted_errors
|
||||
suppress_critical_dialog_on_stderr_match=already_unmounted_errors,
|
||||
show_dialog=show_dialog_on_error # <--- 将 show_dialog_on_error 传递给底层命令执行器
|
||||
)
|
||||
|
||||
if success:
|
||||
@@ -460,52 +459,67 @@ class DiskOperations:
|
||||
|
||||
def format_partition(self, device_path, fstype=None):
|
||||
"""
|
||||
格式化指定分区。
|
||||
启动一个 QInputDialog 让用户选择文件系统类型,然后将格式化操作提交到后台线程。
|
||||
:param device_path: 要格式化的分区路径。
|
||||
:param fstype: 文件系统类型 (例如 'ext4', 'xfs', 'fat32', 'ntfs')。如果为 None,则弹出对话框让用户选择。
|
||||
:return: True 如果成功,否则 False。
|
||||
:return: True 如果成功启动后台任务,否则 False。
|
||||
"""
|
||||
if fstype is None:
|
||||
items = ("ext4", "xfs", "fat32", "ntfs")
|
||||
fstype, ok = QInputDialog.getItem(None, "选择文件系统", "请选择要使用的文件系统类型:", items, 0, False)
|
||||
fstype, ok = QInputDialog.getItem(None, "选择文件系统", f"请选择要使用的文件系统类型 for {device_path}:", items, 0, False)
|
||||
if not ok or not fstype:
|
||||
logger.info("用户取消了文件系统选择。")
|
||||
return False
|
||||
|
||||
reply = QMessageBox.question(None, "确认格式化分区",
|
||||
f"您确定要将分区 {device_path} 格式化为 {fstype} 吗?此操作将擦除分区上的所有数据!",
|
||||
reply = QMessageBox.question(None, "确认格式化",
|
||||
f"您确定要格式化设备 {device_path} 为 {fstype} 吗?此操作将擦除所有数据!",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
if reply == QMessageBox.No:
|
||||
logger.info(f"用户取消了格式化分区 {device_path} 的操作。")
|
||||
logger.info(f"用户取消了格式化 {device_path} 的操作。")
|
||||
return False
|
||||
|
||||
# 尝试卸载分区
|
||||
self.unmount_partition(device_path, show_dialog_on_error=False) # 静默卸载
|
||||
# 检查是否已有格式化任务正在进行
|
||||
if device_path in self.active_format_workers:
|
||||
QMessageBox.warning(None, "警告", f"设备 {device_path} 正在格式化中,请勿重复操作。")
|
||||
return False
|
||||
|
||||
# 尝试卸载分区(静默,不弹对话框)
|
||||
self.unmount_partition(device_path, show_dialog_on_error=False)
|
||||
# 从 fstab 中移除条目 (因为格式化会改变 UUID)
|
||||
self._remove_fstab_entry(device_path)
|
||||
|
||||
logger.info(f"尝试将分区 {device_path} 格式化为 {fstype}。")
|
||||
format_cmd = []
|
||||
if fstype == "ext4":
|
||||
format_cmd = ["mkfs.ext4", "-F", device_path] # -F 强制执行
|
||||
elif fstype == "xfs":
|
||||
format_cmd = ["mkfs.xfs", "-f", device_path] # -f 强制执行
|
||||
elif fstype == "fat32":
|
||||
format_cmd = ["mkfs.fat", "-F", "32", device_path]
|
||||
elif fstype == "ntfs":
|
||||
format_cmd = ["mkfs.ntfs", "-f", device_path]
|
||||
else:
|
||||
QMessageBox.critical(None, "错误", f"不支持的文件系统类型: {fstype}")
|
||||
logger.error(f"不支持的文件系统类型: {fstype}")
|
||||
return False
|
||||
# 创建 QThread 和 FormatWorker 实例
|
||||
thread = QThread()
|
||||
# 将 self._execute_shell_command (它内部调用 LvmOperations._execute_shell_command) 传递给工作线程
|
||||
worker = FormatWorker(device_path, fstype, self._execute_shell_command)
|
||||
worker.moveToThread(thread)
|
||||
|
||||
success, _, stderr = self._execute_shell_command(
|
||||
format_cmd,
|
||||
f"格式化分区 {device_path} 失败"
|
||||
)
|
||||
# 连接信号和槽
|
||||
thread.started.connect(worker.run) # 线程启动时执行 worker 的 run 方法
|
||||
worker.finished.connect(lambda s, dp, o, e: self._on_formatting_finished(s, dp, o, e)) # worker 完成时调用处理函数
|
||||
worker.finished.connect(thread.quit) # worker 完成时退出线程
|
||||
worker.finished.connect(worker.deleteLater) # worker 完成时自动删除 worker 对象
|
||||
thread.finished.connect(thread.deleteLater) # 线程退出时自动删除线程对象
|
||||
|
||||
self.active_format_workers[device_path] = thread # 存储线程以便管理
|
||||
self.formatting_started.emit(device_path) # 发出信号通知主界面格式化已开始
|
||||
thread.start() # 启动线程
|
||||
|
||||
QMessageBox.information(None, "开始格式化", f"设备 {device_path} 正在后台格式化为 {fstype}。完成后将通知您。")
|
||||
return True
|
||||
|
||||
def _on_formatting_finished(self, success, device_path, stdout, stderr):
|
||||
"""
|
||||
处理格式化工作线程完成后的结果。此槽函数在主线程中执行。
|
||||
"""
|
||||
if device_path in self.active_format_workers:
|
||||
del self.active_format_workers[device_path] # 从跟踪列表中移除
|
||||
|
||||
# 在主线程中显示最终结果的消息框
|
||||
if success:
|
||||
QMessageBox.information(None, "成功", f"分区 {device_path} 已成功格式化为 {fstype}。")
|
||||
return True
|
||||
QMessageBox.information(None, "格式化成功", f"设备 {device_path} 已成功格式化。")
|
||||
else:
|
||||
return False
|
||||
QMessageBox.critical(None, "格式化失败", f"格式化设备 {device_path} 失败。\n错误详情: {stderr}")
|
||||
|
||||
self.formatting_finished.emit(success, device_path, stdout, stderr) # 发出信号通知 MainWindow 刷新界面
|
||||
|
||||
|
||||
Reference in New Issue
Block a user