From d2fc416283ff6fd8b1edb78fd5306f43da9e8d52 Mon Sep 17 00:00:00 2001
From: zjing <1052308357@qq.com>
Date: Sun, 1 Feb 2026 17:41:32 +0800
Subject: [PATCH] first commit
---
.qtcreator/pyproject.toml.user | 342 ++++++++++++++++++++
README.md | 0
__pycache__/disk_operations.cpython-314.pyc | Bin 0 -> 12911 bytes
__pycache__/logger_config.cpython-314.pyc | Bin 0 -> 3130 bytes
__pycache__/system_info.cpython-314.pyc | Bin 0 -> 9288 bytes
__pycache__/ui_form.cpython-314.pyc | Bin 0 -> 8131 bytes
disk_operations.py | 237 ++++++++++++++
form.ui | 141 ++++++++
logger_config.py | 54 ++++
mainwindow.py | 189 +++++++++++
pyproject.toml | 5 +
requirements.txt | 1 +
system_info.py | 147 +++++++++
ui_form.py | 102 ++++++
14 files changed, 1218 insertions(+)
create mode 100644 .qtcreator/pyproject.toml.user
create mode 100644 README.md
create mode 100644 __pycache__/disk_operations.cpython-314.pyc
create mode 100644 __pycache__/logger_config.cpython-314.pyc
create mode 100644 __pycache__/system_info.cpython-314.pyc
create mode 100644 __pycache__/ui_form.cpython-314.pyc
create mode 100644 disk_operations.py
create mode 100644 form.ui
create mode 100644 logger_config.py
create mode 100644 mainwindow.py
create mode 100644 pyproject.toml
create mode 100644 requirements.txt
create mode 100644 system_info.py
create mode 100644 ui_form.py
diff --git a/.qtcreator/pyproject.toml.user b/.qtcreator/pyproject.toml.user
new file mode 100644
index 0000000..5a508a6
--- /dev/null
+++ b/.qtcreator/pyproject.toml.user
@@ -0,0 +1,342 @@
+
+
+
+
+
+ EnvironmentId
+ {d278bd62-883b-4816-a712-738287b946d3}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ true
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 0
+ 80
+ true
+ true
+ 1
+ 0
+ false
+ true
+ false
+ 2
+ true
+ true
+ 0
+ 8
+ true
+ false
+ 1
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ false
+ true
+ true
+ true
+ true
+
+ false
+
+
+ 0
+ true
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 2
+ true
+
+
+
+ true
+
+ 0
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ true
+ Python 3.14.2
+ Python 3.14.2
+ {bf68bfb7-0daf-4abe-a8f9-98ada8c9b297}
+ 0
+ 0
+ 0
+
+ /home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv
+
+
+ true
+ Python.PysideBuildStep
+ /home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/pyside6-project
+ /home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/pyside6-uic
+
+ 1
+ 构建
+ 构建
+ ProjectExplorer.BuildSteps.Build
+
+
+ 0
+ 清除
+ 清除
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Python 3.14.2 Virtual Environment
+ Python.PySideBuildConfiguration
+ 0
+ 0
+
+
+ 0
+ 部署
+ 部署
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+ 1
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ mainwindow.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/mainwindow.py
+ true
+
+ /home/smart/qtpj/diskmananger/mainwindow.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ system_info.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/system_info.py
+ true
+
+ /home/smart/qtpj/diskmananger/system_info.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ logger_config.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/logger_config.py
+ true
+
+ /home/smart/qtpj/diskmananger/logger_config.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ disk_operations.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/disk_operations.py
+ true
+
+ /home/smart/qtpj/diskmananger/disk_operations.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+ 4
+ /home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/python
+ /home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv
+
+ 1
+
+
+ 0
+ 部署
+ 部署
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+ 1
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ mainwindow.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/mainwindow.py
+ true
+
+ /home/smart/qtpj/diskmananger/mainwindow.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ system_info.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/system_info.py
+ true
+
+ /home/smart/qtpj/diskmananger/system_info.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ logger_config.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/logger_config.py
+ true
+
+ /home/smart/qtpj/diskmananger/logger_config.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ disk_operations.py
+ PythonEditor.RunConfiguration.
+ /home/smart/qtpj/diskmananger/disk_operations.py
+ true
+
+ /home/smart/qtpj/diskmananger/disk_operations.py
+ true
+ true
+ /home/smart/qtpj/diskmananger
+ :0.0
+
+ 4
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 1
+
+
+ Version
+ 22
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/__pycache__/disk_operations.cpython-314.pyc b/__pycache__/disk_operations.cpython-314.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a66175c37eaaacc193ea3410fa1c4e64119ed6c3
GIT binary patch
literal 12911
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
literal 0
HcmV?d00001
diff --git a/__pycache__/logger_config.cpython-314.pyc b/__pycache__/logger_config.cpython-314.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ae642857ea39c58a1683cdeeef0fd40a4c8eca2a
GIT binary patch
literal 3130
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
F{U7(d>Hz=%
literal 0
HcmV?d00001
diff --git a/disk_operations.py b/disk_operations.py
new file mode 100644
index 0000000..929aac1
--- /dev/null
+++ b/disk_operations.py
@@ -0,0 +1,237 @@
+# disk_operations.py
+import os
+import logging
+from PySide6.QtWidgets import QMessageBox, QInputDialog
+from system_info import SystemInfoManager # 引入 SystemInfoManager 来复用 _run_command
+
+logger = logging.getLogger(__name__) # 获取当前模块的 logger 实例
+
+class DiskOperations:
+ def __init__(self):
+ self.system_manager = SystemInfoManager() # 复用 SystemInfoManager 的命令执行器
+
+ def _get_device_path(self, device_name):
+ """
+ 辅助函数:将设备名(如 'sda1')转换为完整路径(如 '/dev/sda1')。
+ """
+ if not device_name.startswith('/dev/'):
+ return f'/dev/{device_name}'
+ return device_name
+
+ def mount_partition(self, device_name, mount_point=None):
+ """
+ 挂载指定的分区。
+ 如果未提供挂载点,会尝试查找现有挂载点或提示用户输入。
+ """
+ dev_path = self._get_device_path(device_name)
+ logger.info(f"尝试挂载设备: {dev_path}")
+
+ if not mount_point:
+ # 尝试从系统信息中获取当前挂载点
+ devices = self.system_manager.get_block_devices()
+ found_mount_point = None
+ for dev in devices:
+ if dev.get('name') == device_name:
+ found_mount_point = dev.get('mountpoint')
+ break
+ if 'children' in dev:
+ for child in dev['children']:
+ if child.get('name') == device_name:
+ found_mount_point = child.get('mountpoint')
+ break
+ if found_mount_point:
+ break
+
+ if found_mount_point and found_mount_point != '[SWAP]' and found_mount_point != '':
+ # 如果设备已经有挂载点,并且不是SWAP,则直接使用
+ mount_point = found_mount_point
+ logger.info(f"设备 {dev_path} 已经挂载到 {mount_point}。")
+ QMessageBox.information(None, "信息", f"设备 {dev_path} 已经挂载到 {mount_point}。")
+ return True # 已经挂载,视为成功
+ else:
+ # 如果没有挂载点,或者挂载点是SWAP,则提示用户输入
+ mount_point, ok = QInputDialog.getText(None, "挂载分区",
+ f"请输入 {dev_path} 的挂载点 (例如: /mnt/data):",
+ text=f"/mnt/{device_name}")
+ if not ok or not mount_point:
+ logger.info("用户取消了挂载操作或未提供挂载点。")
+ return False
+
+ # 确保挂载点目录存在
+ if not os.path.exists(mount_point):
+ try:
+ os.makedirs(mount_point, exist_ok=True)
+ logger.info(f"创建挂载点目录: {mount_point}")
+ except OSError as e:
+ logger.error(f"创建挂载点目录失败 {mount_point}: {e}")
+ QMessageBox.critical(None, "错误", f"创建挂载点目录失败: {e}")
+ return False
+
+ try:
+ stdout, stderr = self.system_manager._run_command(["mount", dev_path, mount_point], root_privilege=True)
+ logger.info(f"成功挂载 {dev_path} 到 {mount_point}")
+ QMessageBox.information(None, "成功", f"成功挂载 {dev_path} 到 {mount_point}")
+ return True
+ except Exception as e:
+ logger.error(f"挂载 {dev_path} 失败: {e}")
+ QMessageBox.critical(None, "错误", f"挂载 {dev_path} 失败: {e}")
+ return False
+
+ def unmount_partition(self, device_name):
+ """
+ 卸载指定的分区。
+ """
+ dev_path = self._get_device_path(device_name)
+ logger.info(f"尝试卸载设备: {dev_path}")
+
+ # 尝试从系统信息中获取当前挂载点
+ current_mount_point = None
+ devices = self.system_manager.get_block_devices()
+ for dev in devices:
+ if dev.get('name') == device_name:
+ current_mount_point = dev.get('mountpoint')
+ break
+ if 'children' in dev:
+ for child in dev['children']:
+ if child.get('name') == device_name:
+ current_mount_point = child.get('mountpoint')
+ break
+ if current_mount_point:
+ break
+
+ if not current_mount_point or current_mount_point == '[SWAP]' or current_mount_point == '':
+ logger.warning(f"设备 {dev_path} 未挂载或无法确定挂载点,无法卸载。")
+ QMessageBox.warning(None, "警告", f"设备 {dev_path} 未挂载或无法确定挂载点,无法卸载。")
+ return False
+
+ try:
+ # 尝试通过挂载点卸载,通常更可靠
+ stdout, stderr = self.system_manager._run_command(["umount", current_mount_point], root_privilege=True)
+ logger.info(f"成功卸载 {current_mount_point} ({dev_path})")
+ QMessageBox.information(None, "成功", f"成功卸载 {current_mount_point} ({dev_path})")
+ return True
+ except Exception as e:
+ logger.error(f"卸载 {current_mount_point} ({dev_path}) 失败: {e}")
+ QMessageBox.critical(None, "错误", f"卸载 {current_mount_point} ({dev_path}) 失败: {e}")
+ return False
+
+ def delete_partition(self, device_name):
+ """
+ 删除指定的分区。
+ 此操作不可逆,会丢失数据。
+ """
+ dev_path = self._get_device_path(device_name)
+ logger.info(f"尝试删除分区: {dev_path}")
+
+ # 安全检查: 确保是分区,而不是整个磁盘
+ # 简单的检查方法是看设备名是否包含数字 (如 sda1, nvme0n1p1)
+ if not any(char.isdigit() for char in device_name):
+ QMessageBox.warning(None, "警告", f"{dev_path} 看起来不是一个分区。为安全起见,不执行删除操作。")
+ logger.warning(f"尝试删除整个磁盘 {dev_path},已阻止。")
+ return False
+
+ reply = QMessageBox.question(None, "确认删除分区",
+ f"你确定要删除分区 {dev_path} 吗?\n"
+ "此操作不可逆,将导致数据丢失!",
+ QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
+ if reply == QMessageBox.No:
+ logger.info(f"用户取消了删除分区 {dev_path} 的操作。")
+ return False
+
+ # 首先尝试卸载分区(如果已挂载)
+ try:
+ # umount 如果设备未挂载会返回非零退出码,所以这里 check_output=False
+ self.system_manager._run_command(["umount", dev_path], root_privilege=True, check_output=False)
+ logger.info(f"尝试卸载 {dev_path} (如果已挂载)。")
+ except Exception as e:
+ logger.warning(f"卸载 {dev_path} 失败或未挂载: {e}")
+ # 继续执行,因为即使卸载失败也可能能删除
+
+ # 使用 parted 来删除分区
+ # parted 需要父磁盘设备名和分区号
+ # 例如,对于 /dev/sda1,需要 /dev/sda 和分区号 1
+ partition_number_str = ''.join(filter(str.isdigit, device_name)) # 从 sda1 提取 "1"
+ if not partition_number_str:
+ QMessageBox.critical(None, "错误", f"无法从 {device_name} 解析分区号。")
+ logger.error(f"无法从 {device_name} 解析分区号。")
+ return False
+
+ parent_disk_name = device_name.rstrip(partition_number_str) # 从 sda1 提取 "sda"
+ parent_disk_path = self._get_device_path(parent_disk_name)
+
+ try:
+ # parted -s /dev/sda rm 1
+ stdout, stderr = self.system_manager._run_command(
+ ["parted", "-s", parent_disk_path, "rm", partition_number_str],
+ root_privilege=True
+ )
+ logger.info(f"成功删除分区 {dev_path}")
+ QMessageBox.information(None, "成功", f"成功删除分区 {dev_path}")
+ return True
+ except Exception as e:
+ logger.error(f"删除分区 {dev_path} 失败: {e}")
+ QMessageBox.critical(None, "错误", f"删除分区 {dev_path} 失败: {e}")
+ return False
+
+ def format_partition(self, device_name, fstype=None):
+ """
+ 格式化指定的分区。
+ 此操作不可逆,会丢失数据。
+ """
+ dev_path = self._get_device_path(device_name)
+ logger.info(f"尝试格式化分区: {dev_path}")
+
+ # 安全检查: 确保是分区
+ if not any(char.isdigit() for char in device_name):
+ QMessageBox.warning(None, "警告", f"{dev_path} 看起来不是一个分区。为安全起见,不执行格式化操作。")
+ logger.warning(f"尝试格式化整个磁盘 {dev_path},已阻止。")
+ return False
+
+ if not fstype:
+ # 提示用户选择文件系统类型
+ fstypes = ["ext4", "xfs", "fat32", "ntfs"] # 常用文件系统
+ fstype, ok = QInputDialog.getItem(None, "格式化分区",
+ f"请选择 {dev_path} 的文件系统类型:",
+ fstypes, 0, False) # 默认选择 ext4
+ if not ok or not fstype:
+ logger.info("用户取消了格式化操作或未选择文件系统。")
+ return False
+
+ reply = QMessageBox.question(None, "确认格式化分区",
+ f"你确定要格式化分区 {dev_path} 为 {fstype} 吗?\n"
+ "此操作不可逆,将导致数据丢失!",
+ QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
+ if reply == QMessageBox.No:
+ logger.info(f"用户取消了格式化分区 {dev_path} 的操作。")
+ return False
+
+ # 首先尝试卸载分区(如果已挂载)
+ try:
+ self.system_manager._run_command(["umount", dev_path], root_privilege=True, check_output=False)
+ logger.info(f"尝试卸载 {dev_path} (如果已挂载)。")
+ except Exception as e:
+ logger.warning(f"卸载 {dev_path} 失败或未挂载: {e}")
+
+ # 执行 mkfs 命令
+ try:
+ mkfs_cmd = []
+ if fstype == "ext4":
+ mkfs_cmd = ["mkfs.ext4", "-F", dev_path] # -F 强制执行
+ elif fstype == "xfs":
+ mkfs_cmd = ["mkfs.xfs", "-f", dev_path] # -f 强制执行
+ elif fstype == "fat32":
+ mkfs_cmd = ["mkfs.fat", "-F", "32", dev_path]
+ elif fstype == "ntfs":
+ mkfs_cmd = ["mkfs.ntfs", "-f", dev_path] # -f 强制执行
+ else:
+ raise ValueError(f"不支持的文件系统类型: {fstype}")
+
+ stdout, stderr = self.system_manager._run_command(mkfs_cmd, root_privilege=True)
+ logger.info(f"成功格式化分区 {dev_path} 为 {fstype}")
+ QMessageBox.information(None, "成功", f"成功格式化分区 {dev_path} 为 {fstype}")
+ return True
+ except Exception as e:
+ logger.error(f"格式化分区 {dev_path} 失败: {e}")
+ QMessageBox.critical(None, "错误", f"格式化分区 {dev_path} 失败: {e}")
+ return False
+
diff --git a/form.ui b/form.ui
new file mode 100644
index 0000000..cc653e4
--- /dev/null
+++ b/form.ui
@@ -0,0 +1,141 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1000
+ 700
+
+
+
+ Linux 存储管理工具
+
+
+
+ -
+
+
+ 0
+
+
+
+ 块设备概览
+
+
+
-
+
+
+
+ 设备名
+
+
+
+
+ 类型
+
+
+
+
+ 大小
+
+
+
+
+ 挂载点
+
+
+
+
+ 文件系统
+
+
+
+
+ 只读
+
+
+
+
+ UUID
+
+
+
+
+ PARTUUID
+
+
+
+
+ 厂商
+
+
+
+
+ 型号
+
+
+
+
+ 序列号
+
+
+
+
+ 主次号
+
+
+
+
+ 父设备名
+
+
+
+
+
+
+
+
+ RAID 管理
+
+
+
+
+ LVM 管理
+
+
+
+
+ -
+
+
+ 刷新数据
+
+
+
+ -
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/logger_config.py b/logger_config.py
new file mode 100644
index 0000000..07474ea
--- /dev/null
+++ b/logger_config.py
@@ -0,0 +1,54 @@
+# logger_config.py
+import logging
+from PySide6.QtWidgets import QTextEdit
+from PySide6.QtCore import Signal, QObject
+
+class QTextEditLogger(logging.Handler, QObject):
+ """
+ 自定义的 logging 处理器,将日志消息发送到 QTextEdit 控件。
+ """
+ # 定义一个信号,用于在 GUI 线程中更新 QTextEdit
+ append_text = Signal(str)
+
+ def __init__(self, widget: QTextEdit):
+ super().__init__()
+ QObject.__init__(self) # 初始化 QObject 部分
+ self.widget = widget
+ self.widget.setReadOnly(True) # 确保日志区域是只读的
+ # 连接信号到 QTextEdit 的 append 方法
+ self.append_text.connect(self.widget.append)
+
+ def emit(self, record):
+ """
+ 处理日志记录,将其格式化并通过信号发送到 QTextEdit。
+ """
+ msg = self.format(record)
+ self.append_text.emit(msg)
+
+def setup_logging(text_edit_widget: QTextEdit):
+ """
+ 配置全局 logging,使其输出到控制台和指定的 QTextEdit 控件。
+ """
+ root_logger = logging.getLogger()
+ # 设置日志级别,可以根据需要调整 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
+ root_logger.setLevel(logging.INFO)
+
+ # 清除现有的处理器,防止重复输出(如果多次调用此函数)
+ for handler in root_logger.handlers[:]:
+ root_logger.removeHandler(handler)
+
+ # 1. 控制台处理器 (可选,用于调试)
+ console_handler = logging.StreamHandler()
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
+ console_handler.setFormatter(formatter)
+ root_logger.addHandler(console_handler)
+
+ # 2. QTextEdit 处理器
+ text_edit_handler = QTextEditLogger(text_edit_widget)
+ text_edit_handler.setFormatter(formatter)
+ root_logger.addHandler(text_edit_handler)
+
+ return root_logger
+
+# 获取一个全局的 logger 实例,方便其他模块使用
+logger = logging.getLogger(__name__)
diff --git a/mainwindow.py b/mainwindow.py
new file mode 100644
index 0000000..80d1d32
--- /dev/null
+++ b/mainwindow.py
@@ -0,0 +1,189 @@
+# mainwindow.py
+import sys
+import logging # 导入 logging 模块
+
+from PySide6.QtWidgets import (QApplication, QMainWindow, QTreeWidgetItem,
+ QMessageBox, QHeaderView, QMenu, QInputDialog)
+from PySide6.QtCore import Qt, QPoint
+
+# 导入自动生成的 UI 文件
+from ui_form import Ui_MainWindow
+# 导入我们自己编写的系统信息管理模块
+from system_info import SystemInfoManager
+# 导入日志配置
+from logger_config import setup_logging, logger # 导入日志配置函数和 logger 实例
+# 导入磁盘操作模块
+from disk_operations import DiskOperations
+
+class MainWindow(QMainWindow):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+ # 设置日志输出到 QTextEdit
+ setup_logging(self.ui.logOutputTextEdit)
+ logger.info("应用程序启动。")
+
+ # 初始化系统信息管理器和磁盘操作管理器
+ self.system_manager = SystemInfoManager()
+ self.disk_ops = DiskOperations()
+
+ # 连接刷新按钮的信号到槽函数
+ if hasattr(self.ui, 'refreshButton'):
+ self.ui.refreshButton.clicked.connect(self.refresh_block_devices_info)
+ else:
+ logger.warning("Warning: refreshButton not found in UI. Please add it in form.ui and regenerate ui_form.py.")
+
+ # 启用 treeWidget 的自定义上下文菜单
+ self.ui.treeWidget_block_devices.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.ui.treeWidget_block_devices.customContextMenuRequested.connect(self.show_block_device_context_menu)
+
+ # 初始化时刷新一次数据
+ self.refresh_block_devices_info()
+ logger.info("块设备信息已初始化加载。")
+
+ def refresh_block_devices_info(self):
+ """
+ 刷新块设备信息并显示在 QTreeWidget 中。
+ """
+ self.ui.treeWidget_block_devices.clear() # 清空现有内容
+
+ # 定义所有要显示的列头和对应的 lsblk 字段名
+ columns = [
+ ("设备名", 'name'),
+ ("类型", 'type'),
+ ("大小", 'size'),
+ ("挂载点", 'mountpoint'),
+ ("文件系统", 'fstype'),
+ ("只读", 'ro'),
+ ("UUID", 'uuid'),
+ ("PARTUUID", 'partuuid'),
+ ("厂商", 'vendor'),
+ ("型号", 'model'),
+ ("序列号", 'serial'),
+ ("主次号", 'maj:min'),
+ ("父设备名", 'pkname'),
+ ]
+
+ headers = [col[0] for col in columns]
+ self.field_keys = [col[1] for col in columns] # 保存字段键,供后续使用
+
+ self.ui.treeWidget_block_devices.setColumnCount(len(headers)) # 确保列数正确
+ self.ui.treeWidget_block_devices.setHeaderLabels(headers)
+
+ # 调整列宽以适应内容
+ for i in range(len(headers)):
+ self.ui.treeWidget_block_devices.header().setSectionResizeMode(i, QHeaderView.ResizeToContents)
+
+ try:
+ devices = self.system_manager.get_block_devices()
+ for dev in devices:
+ self._add_device_to_tree(self.ui.treeWidget_block_devices, dev)
+
+ # 自动调整列宽
+ for i in range(len(headers)):
+ self.ui.treeWidget_block_devices.resizeColumnToContents(i)
+ logger.info("块设备信息刷新成功。")
+
+ except Exception as e:
+ QMessageBox.critical(self, "错误", f"刷新块设备信息失败: {e}")
+ logger.error(f"刷新块设备信息失败: {e}")
+
+ def _add_device_to_tree(self, parent_item, dev_data):
+ """
+ 辅助函数,将单个设备及其子设备添加到 QTreeWidget。
+ parent_item 可以是 QTreeWidget 本身,也可以是另一个 QTreeWidgetItem。
+ """
+ item = QTreeWidgetItem(parent_item)
+ for i, key in enumerate(self.field_keys):
+ value = dev_data.get(key)
+ if key == 'ro': # 特殊处理布尔值
+ item.setText(i, "是" if value else "否")
+ elif value is None: # None 值显示为空字符串
+ item.setText(i, "")
+ else:
+ item.setText(i, str(value))
+
+ # 将原始设备数据存储在 item 的 data 属性中,方便后续操作时获取
+ item.setData(0, Qt.UserRole, dev_data)
+
+ # 如果有子设备(分区),也显示出来
+ if 'children' in dev_data:
+ for child in dev_data['children']:
+ self._add_device_to_tree(item, child)
+ item.setExpanded(True) # 默认展开父节点,以便看到分区
+
+ def show_block_device_context_menu(self, pos: QPoint):
+ """
+ 显示块设备列表的右键上下文菜单。
+ """
+ item = self.ui.treeWidget_block_devices.itemAt(pos)
+ if item:
+ dev_data = item.data(0, Qt.UserRole) # 获取存储的原始设备数据
+ if not dev_data:
+ logger.warning(f"无法获取设备 {item.text(0)} 的详细数据。")
+ return
+
+ device_name = dev_data.get('name')
+ device_type = dev_data.get('type')
+ mount_point = dev_data.get('mountpoint')
+
+ menu = QMenu(self)
+
+ # 挂载/卸载操作
+ # 只有 'part' (分区) 和 'disk' (整个磁盘,但通常只挂载分区) 可以被挂载/卸载
+ if device_type in ['part', 'disk']:
+ if not mount_point or mount_point == '' or mount_point == 'N/A': # 未挂载
+ mount_action = menu.addAction(f"挂载 {device_name}...")
+ mount_action.triggered.connect(lambda: self._handle_mount(device_name))
+ elif mount_point != '[SWAP]': # 已挂载且不是SWAP
+ unmount_action = menu.addAction(f"卸载 {device_name}")
+ unmount_action.triggered.connect(lambda: self._handle_unmount(device_name))
+
+ # 分隔符,用于区分操作
+ if menu.actions():
+ menu.addSeparator()
+
+ # 删除分区和格式化操作
+ # 这些操作通常只针对 'part' (分区)
+ if device_type == 'part':
+ delete_action = menu.addAction(f"删除分区 {device_name}")
+ delete_action.triggered.connect(lambda: self._handle_delete_partition(device_name))
+
+ format_action = menu.addAction(f"格式化分区 {device_name}...")
+ format_action.triggered.connect(lambda: self._handle_format_partition(device_name))
+
+ if menu.actions(): # 只有当菜单中有动作时才显示
+ menu.exec(self.ui.treeWidget_block_devices.mapToGlobal(pos))
+ else:
+ logger.info(f"设备 {device_name} 没有可用的操作。")
+ else:
+ logger.info("右键点击了空白区域。")
+
+ def _handle_mount(self, device_name):
+ """处理挂载操作,并刷新UI。"""
+ if self.disk_ops.mount_partition(device_name):
+ self.refresh_block_devices_info() # 操作成功后刷新UI
+
+ def _handle_unmount(self, device_name):
+ """处理卸载操作,并刷新UI。"""
+ if self.disk_ops.unmount_partition(device_name):
+ self.refresh_block_devices_info() # 操作成功后刷新UI
+
+ def _handle_delete_partition(self, device_name):
+ """处理删除分区操作,并刷新UI。"""
+ if self.disk_ops.delete_partition(device_name):
+ self.refresh_block_devices_info() # 操作成功后刷新UI
+
+ def _handle_format_partition(self, device_name):
+ """处理格式化分区操作,并刷新UI。"""
+ if self.disk_ops.format_partition(device_name):
+ self.refresh_block_devices_info() # 操作成功后刷新UI
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ widget = MainWindow()
+ widget.show()
+ sys.exit(app.exec())
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..b9a0987
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,5 @@
+[project]
+name = "PySide Widgets Project"
+
+[tool.pyside6-project]
+files = ["disk_operations.py", "form.ui", "logger_config.py", "mainwindow.py", "system_info.py"]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..7fa34f0
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+PySide6
diff --git a/system_info.py b/system_info.py
new file mode 100644
index 0000000..a9bb265
--- /dev/null
+++ b/system_info.py
@@ -0,0 +1,147 @@
+# system_info.py
+import subprocess
+import json
+import os
+import logging # 导入 logging 模块
+
+logger = logging.getLogger(__name__) # 获取当前模块的 logger 实例
+
+class SystemInfoManager:
+ def __init__(self):
+ pass
+
+ def _run_command(self, cmd, root_privilege=False, check_output=True):
+ """
+ 执行shell命令并返回stdout和stderr。
+ 如果需要root权限,会尝试使用sudo。
+ 所有输出和错误都会通过 logger 记录。
+ """
+ full_cmd = []
+ if root_privilege:
+ # 检查当前是否已经是root用户
+ if os.geteuid() != 0:
+ full_cmd.append("sudo")
+
+ full_cmd.extend(cmd)
+ cmd_str = ' '.join(full_cmd) # 用于日志记录的完整命令字符串
+
+ logger.info(f"执行命令: {cmd_str}")
+
+ try:
+ # text=True 自动解码为字符串,encoding='utf-8' 确保正确处理中文
+ # check=check_output 表示如果命令返回非零退出码,则抛出CalledProcessError
+ result = subprocess.run(
+ full_cmd,
+ capture_output=True,
+ text=True,
+ check=check_output,
+ encoding='utf-8',
+ # 设置LANG环境变量,确保命令输出使用UTF-8编码,避免解析问题
+ env=dict(os.environ, LANG="en_US.UTF-8")
+ )
+ if result.stdout:
+ logger.debug(f"命令输出 (stdout):\n{result.stdout.strip()}")
+ if result.stderr:
+ logger.warning(f"命令输出 (stderr):\n{result.stderr.strip()}")
+ return result.stdout.strip(), result.stderr.strip()
+ except subprocess.CalledProcessError as e:
+ # 捕获命令执行失败的情况
+ error_msg = f"命令执行失败: {cmd_str}\n" \
+ f"退出码: {e.returncode}\n" \
+ f"标准输出: {e.stdout}\n" \
+ f"标准错误: {e.stderr}"
+ logger.error(error_msg) # 使用 logger 记录错误
+ raise ValueError(error_msg) # 抛出更易于处理的异常
+ except FileNotFoundError:
+ error_msg = f"命令未找到: {full_cmd[0]}。请确保已安装。"
+ logger.error(error_msg)
+ raise FileNotFoundError(error_msg)
+ except Exception as e:
+ error_msg = f"执行命令时发生未知错误: {e}"
+ logger.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ def get_block_devices(self):
+ """
+ 获取所有块设备的信息,以JSON格式返回。
+ 使用 lsblk -J 命令。
+ """
+ try:
+ stdout, _ = self._run_command(["lsblk", "-J", "-o", "NAME,FSTYPE,SIZE,MOUNTPOINT,RO,TYPE,UUID,PARTUUID,VENDOR,MODEL,SERIAL,MAJ:MIN,PKNAME"], root_privilege=False)
+ data = json.loads(stdout)
+ logger.info("成功获取块设备信息。")
+ return data.get('blockdevices', [])
+ except Exception as e:
+ logger.error(f"获取块设备信息失败: {e}") # 使用 logger 记录错误
+ return []
+
+ def get_mdadm_arrays(self):
+ """
+ 获取所有RAID阵列的详细信息。
+ 使用 mdadm --detail --scan 命令。
+ """
+ try:
+ stdout, _ = self._run_command(["mdadm", "--detail", "--scan"], root_privilege=False)
+ logger.info("成功获取RAID阵列信息。")
+ return stdout
+ except Exception as e:
+ logger.error(f"获取RAID阵列信息失败: {e}")
+ return "无法获取RAID阵列信息。"
+
+ def get_lvm_info(self):
+ """
+ 获取LVM的物理卷、卷组、逻辑卷信息。
+ 使用 pvs, vgs, lvs 命令,并尝试获取JSON格式。
+ """
+ lvm_info = {}
+ try:
+ stdout_pvs, _ = self._run_command(["pvs", "--reportformat", "json"], root_privilege=False)
+ lvm_info['pvs'] = json.loads(stdout_pvs).get('report', [])[0].get('pv', [])
+ logger.info("成功获取物理卷信息。")
+ except Exception as e:
+ logger.error(f"获取物理卷信息失败: {e}")
+ lvm_info['pvs'] = []
+
+ try:
+ stdout_vgs, _ = self._run_command(["vgs", "--reportformat", "json"], root_privilege=False)
+ lvm_info['vgs'] = json.loads(stdout_vgs).get('report', [])[0].get('vg', [])
+ logger.info("成功获取卷组信息。")
+ except Exception as e:
+ logger.error(f"获取卷组信息失败: {e}")
+ lvm_info['vgs'] = []
+
+ try:
+ stdout_lvs, _ = self._run_command(["lvs", "--reportformat", "json"], root_privilege=False)
+ lvm_info['lvs'] = json.loads(stdout_lvs).get('report', [])[0].get('lv', [])
+ logger.info("成功获取逻辑卷信息。")
+ except Exception as e:
+ logger.error(f"获取逻辑卷信息失败: {e}")
+ lvm_info['lvs'] = []
+
+ return lvm_info
+
+# 示例用法 (可以在此模块中添加测试代码)
+if __name__ == "__main__":
+ # 为了在单独运行时也能看到日志输出,这里简单配置一下
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+ manager = SystemInfoManager()
+ logger.info("--- 块设备信息 ---")
+ devices = manager.get_block_devices()
+ for dev in devices:
+ logger.info(f" NAME: {dev.get('name')}, TYPE: {dev.get('type')}, SIZE: {dev.get('size')}, MOUNTPOINT: {dev.get('mountpoint')}")
+
+ logger.info("\n--- RAID 阵列信息 ---")
+ raid_info = manager.get_mdadm_arrays()
+ logger.info(raid_info)
+
+ logger.info("\n--- LVM 信息 ---")
+ lvm_info = manager.get_lvm_info()
+ logger.info("物理卷 (PVs):")
+ for pv in lvm_info['pvs']:
+ logger.info(f" {pv.get('pv_name')} (VG: {pv.get('vg_name')}, Size: {pv.get('pv_size')})")
+ logger.info("卷组 (VGs):")
+ for vg in lvm_info['vgs']:
+ logger.info(f" {vg.get('vg_name')} (Size: {vg.get('vg_size')}, PVs: {vg.get('pv_count')}, LVs: {vg.get('lv_count')})")
+ logger.info("逻辑卷 (LVs):")
+ for lv in lvm_info['lvs']:
+ logger.info(f" {lv.get('lv_name')} (VG: {lv.get('vg_name')}, Size: {lv.get('lv_size')})")
diff --git a/ui_form.py b/ui_form.py
new file mode 100644
index 0000000..dc228af
--- /dev/null
+++ b/ui_form.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'form.ui'
+##
+## Created by: Qt User Interface Compiler version 6.10.1
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QHeaderView, QMainWindow, QMenuBar,
+ QPushButton, QSizePolicy, QStatusBar, QTabWidget,
+ QTextEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
+ QWidget)
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(1000, 700)
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.verticalLayout = QVBoxLayout(self.centralwidget)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.tabWidget = QTabWidget(self.centralwidget)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tab_block_devices = QWidget()
+ self.tab_block_devices.setObjectName(u"tab_block_devices")
+ self.verticalLayout_2 = QVBoxLayout(self.tab_block_devices)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.treeWidget_block_devices = QTreeWidget(self.tab_block_devices)
+ self.treeWidget_block_devices.setObjectName(u"treeWidget_block_devices")
+
+ self.verticalLayout_2.addWidget(self.treeWidget_block_devices)
+
+ self.tabWidget.addTab(self.tab_block_devices, "")
+ self.tab_raid = QWidget()
+ self.tab_raid.setObjectName(u"tab_raid")
+ self.tabWidget.addTab(self.tab_raid, "")
+ self.tab_lvm = QWidget()
+ self.tab_lvm.setObjectName(u"tab_lvm")
+ self.tabWidget.addTab(self.tab_lvm, "")
+
+ self.verticalLayout.addWidget(self.tabWidget)
+
+ self.refreshButton = QPushButton(self.centralwidget)
+ self.refreshButton.setObjectName(u"refreshButton")
+
+ self.verticalLayout.addWidget(self.refreshButton)
+
+ self.logOutputTextEdit = QTextEdit(self.centralwidget)
+ self.logOutputTextEdit.setObjectName(u"logOutputTextEdit")
+ self.logOutputTextEdit.setReadOnly(True)
+
+ self.verticalLayout.addWidget(self.logOutputTextEdit)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(MainWindow)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 1000, 22))
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.retranslateUi(MainWindow)
+
+ self.tabWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Linux \u5b58\u50a8\u7ba1\u7406\u5de5\u5177", None))
+ ___qtreewidgetitem = self.treeWidget_block_devices.headerItem()
+ ___qtreewidgetitem.setText(12, QCoreApplication.translate("MainWindow", u"\u7236\u8bbe\u5907\u540d", None));
+ ___qtreewidgetitem.setText(11, QCoreApplication.translate("MainWindow", u"\u4e3b\u6b21\u53f7", None));
+ ___qtreewidgetitem.setText(10, QCoreApplication.translate("MainWindow", u"\u5e8f\u5217\u53f7", None));
+ ___qtreewidgetitem.setText(9, QCoreApplication.translate("MainWindow", u"\u578b\u53f7", None));
+ ___qtreewidgetitem.setText(8, QCoreApplication.translate("MainWindow", u"\u5382\u5546", None));
+ ___qtreewidgetitem.setText(7, QCoreApplication.translate("MainWindow", u"PARTUUID", None));
+ ___qtreewidgetitem.setText(6, QCoreApplication.translate("MainWindow", u"UUID", None));
+ ___qtreewidgetitem.setText(5, QCoreApplication.translate("MainWindow", u"\u53ea\u8bfb", None));
+ ___qtreewidgetitem.setText(4, QCoreApplication.translate("MainWindow", u"\u6587\u4ef6\u7cfb\u7edf", None));
+ ___qtreewidgetitem.setText(3, QCoreApplication.translate("MainWindow", u"\u6302\u8f7d\u70b9", None));
+ ___qtreewidgetitem.setText(2, QCoreApplication.translate("MainWindow", u"\u5927\u5c0f", None));
+ ___qtreewidgetitem.setText(1, QCoreApplication.translate("MainWindow", u"\u7c7b\u578b", None));
+ ___qtreewidgetitem.setText(0, QCoreApplication.translate("MainWindow", u"\u8bbe\u5907\u540d", None));
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_block_devices), QCoreApplication.translate("MainWindow", u"\u5757\u8bbe\u5907\u6982\u89c8", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_raid), QCoreApplication.translate("MainWindow", u"RAID \u7ba1\u7406", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_lvm), QCoreApplication.translate("MainWindow", u"LVM \u7ba1\u7406", None))
+ self.refreshButton.setText(QCoreApplication.translate("MainWindow", u"\u5237\u65b0\u6570\u636e", None))
+ # retranslateUi
+