fix bug45
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE QtCreatorProject>
|
<!DOCTYPE QtCreatorProject>
|
||||||
<!-- Written by QtCreator 18.0.2, 2026-02-02T03:36:12. -->
|
<!-- Written by QtCreator 18.0.2, 2026-02-02T21:58:57. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
@@ -99,13 +99,15 @@
|
|||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{2b4075d3-005b-46f6-8fd5-3bd8a94071db}</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{2b4075d3-005b-46f6-8fd5-3bd8a94071db}</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">2</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2</value>
|
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||||
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Python.PysideBuildStep</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Python.PysideBuildStep</value>
|
||||||
|
<value type="QString" key="Python.PySideProjectTool">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2/bin/pyside6-project</value>
|
||||||
|
<value type="QString" key="Python.PySideUic">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2/bin/pyside6-uic</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
|
||||||
@@ -126,7 +128,7 @@
|
|||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Python 3.14.2 Virtual Environment</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Python 3.14.2 Virtual Environment</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Python.PySideBuildConfiguration</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Python.PySideBuildConfiguration</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">2</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||||
@@ -224,7 +226,70 @@
|
|||||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">4</value>
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.4">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">dialogs.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/dialogs.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/dialogs.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.5">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">raid_operations.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/raid_operations.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/raid_operations.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.6">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">lvm_operations.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/lvm_operations.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/lvm_operations.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">7</value>
|
||||||
<value type="QString" key="python">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2/bin/python</value>
|
<value type="QString" key="python">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2/bin/python</value>
|
||||||
<value type="QString" key="venv">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2</value>
|
<value type="QString" key="venv">/home/jing/qtpj/diskmanager/.qtcreator/Python_3_14_2venv_2</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
@@ -326,7 +391,70 @@
|
|||||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">4</value>
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.4">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">dialogs.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/dialogs.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/dialogs.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.5">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">raid_operations.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/raid_operations.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/raid_operations.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.6">
|
||||||
|
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||||
|
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||||
|
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">lvm_operations.py</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/jing/qtpj/diskmanager/lvm_operations.py</value>
|
||||||
|
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||||
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
|
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/jing/qtpj/diskmanager/lvm_operations.py</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
|
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/jing/qtpj/diskmanager</value>
|
||||||
|
<value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
|
||||||
|
</valuemap>
|
||||||
|
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">7</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
</data>
|
</data>
|
||||||
<data>
|
<data>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
28
dialogs.py
28
dialogs.py
@@ -388,36 +388,46 @@ class CreateLvDialog(QDialog):
|
|||||||
max_size_gb = self.vg_sizes.get(selected_vg, 0.0)
|
max_size_gb = self.vg_sizes.get(selected_vg, 0.0)
|
||||||
|
|
||||||
# 确保 max_size_gb 至少是 spinbox 的最小值,以防卷组可用空间过小导致 UI 问题
|
# 确保 max_size_gb 至少是 spinbox 的最小值,以防卷组可用空间过小导致 UI 问题
|
||||||
if max_size_gb < self.lv_size_spinbox.minimum():
|
current_min_limit = self.lv_size_spinbox.minimum() # 获取当前的最小值限制
|
||||||
# 如果实际最大可用空间小于最小允许值,则将最小值临时调整为实际最大值
|
if max_size_gb < current_min_limit:
|
||||||
|
# 如果实际最大可用空间小于当前最小值限制,则将最小值临时调整为实际最大值
|
||||||
self.lv_size_spinbox.setMinimum(max_size_gb if max_size_gb > 0 else 0.01) # 至少0.01GB
|
self.lv_size_spinbox.setMinimum(max_size_gb if max_size_gb > 0 else 0.01) # 至少0.01GB
|
||||||
else:
|
else:
|
||||||
self.lv_size_spinbox.setMinimum(0.1) # 恢复正常最小值
|
self.lv_size_spinbox.setMinimum(0.1) # 恢复正常最小值
|
||||||
|
|
||||||
self.lv_size_spinbox.setMaximum(max(self.lv_size_spinbox.minimum(), max_size_gb)) # 设置最大值,确保不小于最小值
|
# 计算并设置 spinbox 的最大值
|
||||||
|
clamped_max_gb = max(self.lv_size_spinbox.minimum(), max_size_gb)
|
||||||
|
self.lv_size_spinbox.setMaximum(clamped_max_gb)
|
||||||
|
|
||||||
# 如果选中了“使用最大可用空间”,则将 spinbox 值设置为最大值
|
# 如果选中了“使用最大可用空间”,则将 spinbox 值设置为最大值
|
||||||
if self.use_max_space_checkbox.isChecked():
|
if self.use_max_space_checkbox.isChecked():
|
||||||
self.lv_size_spinbox.setValue(self.lv_size_spinbox.maximum())
|
self.lv_size_spinbox.setValue(clamped_max_gb) # 使用计算出的最大值
|
||||||
else:
|
else:
|
||||||
# 如果当前值超过了新的最大值,则调整为新的最大值
|
# 如果当前值超过了新的最大值,则调整为新的最大值
|
||||||
if self.lv_size_spinbox.value() > self.lv_size_spinbox.maximum():
|
if self.lv_size_spinbox.value() > clamped_max_gb:
|
||||||
self.lv_size_spinbox.setValue(self.lv_size_spinbox.maximum())
|
self.lv_size_spinbox.setValue(clamped_max_gb)
|
||||||
# 如果当前值小于新的最小值,则调整
|
# 如果当前值小于新的最小值,则调整
|
||||||
elif self.lv_size_spinbox.value() < self.lv_size_spinbox.minimum():
|
elif self.lv_size_spinbox.value() < self.lv_size_spinbox.minimum():
|
||||||
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
|
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
|
||||||
|
|
||||||
|
|
||||||
def _toggle_size_input(self, state):
|
def _toggle_size_input(self, state):
|
||||||
"""根据“使用最大可用空间”复选框的状态切换大小输入框的启用/禁用状态。"""
|
"""根据“使用最大可用空间”复选框的状态切换大小输入框的启用/禁用状态。"""
|
||||||
|
# 获取当前选中的卷组的实际最大可用空间(GB)
|
||||||
|
selected_vg = self.vg_combo_box.currentText()
|
||||||
|
actual_max_gb = self.vg_sizes.get(selected_vg, 0.0)
|
||||||
|
|
||||||
|
# 确保计算出的最大值不小于spinbox的最小值
|
||||||
|
clamped_max_gb = max(self.lv_size_spinbox.minimum(), actual_max_gb)
|
||||||
|
|
||||||
if state == Qt.Checked:
|
if state == Qt.Checked:
|
||||||
self.lv_size_spinbox.setDisabled(True)
|
self.lv_size_spinbox.setDisabled(True)
|
||||||
self.lv_size_spinbox.setValue(self.lv_size_spinbox.maximum()) # 设置为最大值
|
self.lv_size_spinbox.setValue(clamped_max_gb) # 使用计算出的最大值
|
||||||
else:
|
else:
|
||||||
self.lv_size_spinbox.setDisabled(False)
|
self.lv_size_spinbox.setDisabled(False)
|
||||||
# 如果之前是最大值,取消勾选后,恢复到最小值或一个合理值
|
# 如果之前是最大值,取消勾选后,恢复到最小值或一个合理值
|
||||||
if self.lv_size_spinbox.value() == self.lv_size_spinbox.maximum():
|
if self.lv_size_spinbox.value() == clamped_max_gb: # 比较时也使用计算出的最大值
|
||||||
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
|
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
|
||||||
|
# 否则,保持当前值(用户手动输入的值)
|
||||||
|
|
||||||
|
|
||||||
def get_lv_info(self):
|
def get_lv_info(self):
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||||
|
from system_info import SystemInfoManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class DiskOperations:
|
class DiskOperations:
|
||||||
def __init__(self):
|
def __init__(self, system_manager: SystemInfoManager): # NEW: 接收 system_manager 实例
|
||||||
pass
|
self.system_manager = system_manager
|
||||||
|
|
||||||
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):
|
||||||
@@ -18,6 +19,7 @@ class DiskOperations:
|
|||||||
:param error_message: 命令失败时显示给用户的错误消息。
|
:param error_message: 命令失败时显示给用户的错误消息。
|
||||||
:param root_privilege: 如果为 True,则使用 sudo 执行命令。
|
:param root_privilege: 如果为 True,则使用 sudo 执行命令。
|
||||||
:param suppress_critical_dialog_on_stderr_match: 如果 stderr 包含此字符串,则不显示关键错误对话框。
|
:param suppress_critical_dialog_on_stderr_match: 如果 stderr 包含此字符串,则不显示关键错误对话框。
|
||||||
|
可以是字符串或字符串元组。
|
||||||
:param input_data: 传递给命令stdin的数据 (str)。
|
:param input_data: 传递给命令stdin的数据 (str)。
|
||||||
:return: (True/False, stdout_str, stderr_str)
|
:return: (True/False, stdout_str, stderr_str)
|
||||||
"""
|
"""
|
||||||
@@ -50,10 +52,17 @@ class DiskOperations:
|
|||||||
logger.error(f"标准输出: {e.stdout.strip()}")
|
logger.error(f"标准输出: {e.stdout.strip()}")
|
||||||
logger.error(f"标准错误: {stderr_output}")
|
logger.error(f"标准错误: {stderr_output}")
|
||||||
|
|
||||||
if suppress_critical_dialog_on_stderr_match and \
|
# Determine if the error dialog should be suppressed by this function
|
||||||
(suppress_critical_dialog_on_stderr_match in stderr_output or \
|
should_suppress_dialog_here = False
|
||||||
(isinstance(suppress_critical_dialog_on_stderr_match, tuple) and \
|
if suppress_critical_dialog_on_stderr_match:
|
||||||
any(s in stderr_output for s in 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}' 匹配抑制条件,不显示关键错误对话框。")
|
logger.info(f"错误信息 '{stderr_output}' 匹配抑制条件,不显示关键错误对话框。")
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(None, "错误", f"{error_message}\n错误详情: {stderr_output}")
|
QMessageBox.critical(None, "错误", f"{error_message}\n错误详情: {stderr_output}")
|
||||||
@@ -81,7 +90,9 @@ class DiskOperations:
|
|||||||
fstab_path = "/etc/fstab"
|
fstab_path = "/etc/fstab"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 检查 fstab 中是否已存在相同 UUID 或挂载点的条目
|
# 检查 fstab 中是否已存在相同 UUID 的条目
|
||||||
|
# 使用 _execute_shell_command 来读取 fstab,尽管通常不需要 sudo
|
||||||
|
# 这里为了简化,直接读取文件,但写入时使用 _execute_shell_command
|
||||||
with open(fstab_path, 'r') as f:
|
with open(fstab_path, 'r') as f:
|
||||||
fstab_content = f.readlines()
|
fstab_content = f.readlines()
|
||||||
|
|
||||||
@@ -90,21 +101,23 @@ class DiskOperations:
|
|||||||
logger.warning(f"设备 {device_path} (UUID={uuid}) 已存在于 fstab 中。跳过添加。")
|
logger.warning(f"设备 {device_path} (UUID={uuid}) 已存在于 fstab 中。跳过添加。")
|
||||||
QMessageBox.information(None, "信息", f"设备 {device_path} (UUID={uuid}) 已存在于 fstab 中。")
|
QMessageBox.information(None, "信息", f"设备 {device_path} (UUID={uuid}) 已存在于 fstab 中。")
|
||||||
return True # 认为成功,因为目标已达成
|
return True # 认为成功,因为目标已达成
|
||||||
# 检查挂载点是否已存在 (可能被其他设备使用)
|
|
||||||
if mount_point in line.split():
|
|
||||||
logger.warning(f"挂载点 {mount_point} 已存在于 fstab 中。跳过添加。")
|
|
||||||
QMessageBox.information(None, "信息", f"挂载点 {mount_point} 已存在于 fstab 中。")
|
|
||||||
return True # 认为成功,因为目标已达成
|
|
||||||
|
|
||||||
# 如果不存在,则追加到 fstab
|
# 如果不存在,则追加到 fstab
|
||||||
with open(fstab_path, 'a') as f:
|
# 使用 _execute_shell_command for sudo write
|
||||||
f.write(fstab_entry + '\n')
|
success, _, stderr = self._execute_shell_command(
|
||||||
|
["sh", "-c", f"echo '{fstab_entry}' >> {fstab_path}"],
|
||||||
|
f"将 {device_path} 添加到 {fstab_path} 失败",
|
||||||
|
root_privilege=True
|
||||||
|
)
|
||||||
|
if success:
|
||||||
logger.info(f"已将 {fstab_entry} 添加到 {fstab_path}。")
|
logger.info(f"已将 {fstab_entry} 添加到 {fstab_path}。")
|
||||||
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功添加到 /etc/fstab。")
|
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功添加到 /etc/fstab。")
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
return False # Error handled by _execute_shell_command
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"写入 {fstab_path} 失败: {e}")
|
logger.error(f"处理 {fstab_path} 失败: {e}")
|
||||||
QMessageBox.critical(None, "错误", f"写入 {fstab_path} 失败: {e}")
|
QMessageBox.critical(None, "错误", f"处理 {fstab_path} 失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _remove_fstab_entry(self, device_path):
|
def _remove_fstab_entry(self, device_path):
|
||||||
@@ -112,49 +125,45 @@ class DiskOperations:
|
|||||||
从 /etc/fstab 中移除指定设备的条目。
|
从 /etc/fstab 中移除指定设备的条目。
|
||||||
此方法需要 SystemInfoManager 来获取设备的 UUID。
|
此方法需要 SystemInfoManager 来获取设备的 UUID。
|
||||||
"""
|
"""
|
||||||
success, stdout, stderr = self._execute_shell_command(
|
# NEW: 使用 system_manager 获取 UUID,它会处理路径解析
|
||||||
["lsblk", "-no", "UUID", device_path],
|
device_details = self.system_manager.get_device_details_by_path(device_path)
|
||||||
f"获取设备 {device_path} 的 UUID 失败",
|
if not device_details or not device_details.get('uuid'):
|
||||||
root_privilege=False, # lsblk 通常不需要 sudo
|
logger.warning(f"无法获取设备 {device_path} 的 UUID,无法从 fstab 中移除。")
|
||||||
suppress_critical_dialog_on_stderr_match=("找不到或无法访问", "No such device or address") # 匹配中英文错误
|
|
||||||
)
|
|
||||||
if not success:
|
|
||||||
logger.warning(f"无法获取设备 {device_path} 的 UUID,无法从 fstab 中移除。错误: {stderr}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
uuid = stdout.strip()
|
|
||||||
if not uuid:
|
|
||||||
logger.warning(f"设备 {device_path} 没有 UUID,无法从 fstab 中移除。")
|
|
||||||
return False
|
return False
|
||||||
|
uuid = device_details.get('uuid')
|
||||||
|
|
||||||
fstab_path = "/etc/fstab"
|
fstab_path = "/etc/fstab"
|
||||||
try:
|
# Use sed for robust removal with sudo
|
||||||
with open(fstab_path, 'r') as f:
|
# suppress_critical_dialog_on_stderr_match is added to handle cases where fstab might not exist
|
||||||
lines = f.readlines()
|
# or sed reports no changes, which is not a critical error for removal.
|
||||||
|
command = ["sed", "-i", f"/UUID={uuid}/d", fstab_path]
|
||||||
new_lines = []
|
success, _, stderr = self._execute_shell_command(
|
||||||
removed = False
|
command,
|
||||||
for line in lines:
|
f"从 {fstab_path} 中删除 UUID={uuid} 的条目失败",
|
||||||
if f"UUID={uuid}" in line:
|
root_privilege=True,
|
||||||
logger.info(f"从 {fstab_path} 中移除了条目: {line.strip()}")
|
suppress_critical_dialog_on_stderr_match=(
|
||||||
removed = True
|
f"sed: {fstab_path}: No such file or directory", # English
|
||||||
else:
|
f"sed: {fstab_path}: 没有那个文件或目录", # Chinese
|
||||||
new_lines.append(line)
|
"no changes were made" # if sed finds nothing to delete
|
||||||
|
)
|
||||||
if removed:
|
)
|
||||||
# 写入临时文件,然后替换原文件
|
if success:
|
||||||
with open(fstab_path + ".tmp", 'w') as f_tmp:
|
logger.info(f"已从 {fstab_path} 中删除 UUID={uuid} 的条目。")
|
||||||
f_tmp.writelines(new_lines)
|
|
||||||
os.rename(fstab_path + ".tmp", fstab_path)
|
|
||||||
logger.info(f"已从 {fstab_path} 中移除 UUID={uuid} 的条目。")
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.info(f"fstab 中未找到 UUID={uuid} 的条目。")
|
# If sed failed, it might be because the entry wasn't found, which is fine.
|
||||||
return False
|
# _execute_shell_command would have suppressed the dialog if it matched the suppress_critical_dialog_on_stderr_match.
|
||||||
except Exception as e:
|
# So, if we reach here, it's either a real error (dialog shown by _execute_shell_command)
|
||||||
logger.error(f"修改 {fstab_path} 失败: {e}")
|
# or a suppressed "no changes" type of error. In both cases, if no real error, we return True.
|
||||||
QMessageBox.critical(None, "错误", f"修改 {fstab_path} 失败: {e}")
|
if any(s in stderr for s in (
|
||||||
return False
|
f"sed: {fstab_path}: No such file or directory",
|
||||||
|
f"sed: {fstab_path}: 没有那个文件或目录",
|
||||||
|
"no changes were made",
|
||||||
|
"No such file or directory" # more general check
|
||||||
|
)):
|
||||||
|
logger.info(f"fstab 中未找到 UUID={uuid} 的条目,无需删除。")
|
||||||
|
return True # Consider it a success if the entry is not there
|
||||||
|
return False # Other errors are already handled by _execute_shell_command
|
||||||
|
|
||||||
def mount_partition(self, device_path, mount_point, add_to_fstab=False):
|
def mount_partition(self, device_path, mount_point, add_to_fstab=False):
|
||||||
"""
|
"""
|
||||||
@@ -181,18 +190,18 @@ class DiskOperations:
|
|||||||
if success:
|
if success:
|
||||||
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功挂载到 {mount_point}。")
|
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功挂载到 {mount_point}。")
|
||||||
if add_to_fstab:
|
if add_to_fstab:
|
||||||
# 为了获取 fstype 和 UUID,需要再次调用 lsblk
|
# NEW: 使用 self.system_manager 获取 fstype 和 UUID
|
||||||
success_details, stdout_details, stderr_details = self._execute_shell_command(
|
device_details = self.system_manager.get_device_details_by_path(device_path)
|
||||||
["lsblk", "-no", "FSTYPE,UUID", device_path],
|
if device_details:
|
||||||
f"获取设备 {device_path} 的文件系统类型和 UUID 失败",
|
fstype = device_details.get('fstype')
|
||||||
root_privilege=False,
|
uuid = device_details.get('uuid')
|
||||||
suppress_critical_dialog_on_stderr_match=("找不到或无法访问", "No such device or address")
|
if fstype and uuid:
|
||||||
)
|
|
||||||
if success_details:
|
|
||||||
fstype, uuid = stdout_details.strip().split()
|
|
||||||
self._add_to_fstab(device_path, mount_point, fstype, uuid)
|
self._add_to_fstab(device_path, mount_point, fstype, uuid)
|
||||||
else:
|
else:
|
||||||
logger.error(f"无法获取设备 {device_path} 的详细信息以添加到 fstab: {stderr_details}")
|
logger.error(f"无法获取设备 {device_path} 的文件系统类型或 UUID 以添加到 fstab。")
|
||||||
|
QMessageBox.warning(None, "警告", f"设备 {device_path} 已挂载,但无法获取文件系统类型或 UUID 以添加到 fstab。")
|
||||||
|
else:
|
||||||
|
logger.error(f"无法获取设备 {device_path} 的详细信息以添加到 fstab。")
|
||||||
QMessageBox.warning(None, "警告", f"设备 {device_path} 已挂载,但无法获取详细信息以添加到 fstab。")
|
QMessageBox.warning(None, "警告", f"设备 {device_path} 已挂载,但无法获取详细信息以添加到 fstab。")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -206,23 +215,34 @@ class DiskOperations:
|
|||||||
:return: True 如果成功,否则 False。
|
:return: True 如果成功,否则 False。
|
||||||
"""
|
"""
|
||||||
logger.info(f"尝试卸载设备 {device_path}。")
|
logger.info(f"尝试卸载设备 {device_path}。")
|
||||||
try:
|
# 定义表示设备已未挂载的错误信息(中英文)
|
||||||
|
# 增加了 "未指定挂载点"
|
||||||
|
already_unmounted_errors = ("not mounted", "未挂载", "未指定挂载点")
|
||||||
|
|
||||||
|
# 调用 _execute_shell_command,并告诉它在遇到“已未挂载”错误时,不要弹出其自身的关键错误对话框。
|
||||||
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="not mounted" if not show_dialog_on_error else None
|
suppress_critical_dialog_on_stderr_match=already_unmounted_errors
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
# 如果命令成功执行,则卸载成功。
|
||||||
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功卸载。")
|
QMessageBox.information(None, "成功", f"设备 {device_path} 已成功卸载。")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if show_dialog_on_error:
|
# 如果命令失败,检查是否是因为设备已经未挂载或挂载点未指定。
|
||||||
QMessageBox.critical(None, "错误", f"卸载设备 {device_path} 失败。\n错误详情: {stderr}")
|
is_already_unmounted_error = any(s in stderr for s in already_unmounted_errors)
|
||||||
return False
|
|
||||||
except Exception as e:
|
if is_already_unmounted_error:
|
||||||
logger.error(f"卸载设备 {device_path} 时发生异常: {e}")
|
logger.info(f"设备 {device_path} 已经处于未挂载状态(或挂载点未指定),无需重复卸载。")
|
||||||
if show_dialog_on_error:
|
# 这种情况下,操作结果符合预期(设备已是未挂载),不弹出对话框,并返回 True。
|
||||||
QMessageBox.critical(None, "错误", f"卸载设备 {device_path} 时发生异常: {e}")
|
return True
|
||||||
|
else:
|
||||||
|
# 对于其他类型的卸载失败(例如设备忙、权限不足等),
|
||||||
|
# _execute_shell_command 应该已经弹出了关键错误对话框
|
||||||
|
# (因为它没有匹配到 `already_unmounted_errors` 进行抑制)。
|
||||||
|
# 所以,这里我们不需要再次弹出对话框,直接返回 False。
|
||||||
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):
|
||||||
@@ -234,13 +254,19 @@ class DiskOperations:
|
|||||||
如果磁盘有分区表但没有空闲空间,返回 (None, None)。
|
如果磁盘有分区表但没有空闲空间,返回 (None, None)。
|
||||||
"""
|
"""
|
||||||
logger.debug(f"尝试获取磁盘 {disk_path} 的空闲空间信息 (MiB)。")
|
logger.debug(f"尝试获取磁盘 {disk_path} 的空闲空间信息 (MiB)。")
|
||||||
|
# suppress_critical_dialog_on_stderr_match is added to handle cases where parted might complain about
|
||||||
|
# an unrecognized disk label, which is expected for a fresh disk.
|
||||||
success, stdout, stderr = self._execute_shell_command(
|
success, stdout, stderr = self._execute_shell_command(
|
||||||
["parted", "-s", disk_path, "unit", "MiB", "print", "free"],
|
["parted", "-s", disk_path, "unit", "MiB", "print", "free"],
|
||||||
f"获取磁盘 {disk_path} 分区信息失败",
|
f"获取磁盘 {disk_path} 分区信息失败",
|
||||||
root_privilege=True
|
root_privilege=True,
|
||||||
|
suppress_critical_dialog_on_stderr_match=("无法辨识的磁盘卷标", "unrecognized disk label")
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
|
# If parted failed and it wasn't due to an unrecognized label (handled by suppress_critical_dialog_on_stderr_match),
|
||||||
|
# then _execute_shell_command would have shown a dialog.
|
||||||
|
# We just log and return None.
|
||||||
logger.error(f"获取磁盘 {disk_path} 空闲空间信息失败: {stderr}")
|
logger.error(f"获取磁盘 {disk_path} 空闲空间信息失败: {stderr}")
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@@ -294,23 +320,31 @@ class DiskOperations:
|
|||||||
|
|
||||||
# 1. 检查磁盘是否有分区表
|
# 1. 检查磁盘是否有分区表
|
||||||
has_partition_table = False
|
has_partition_table = False
|
||||||
try:
|
# Use suppress_critical_dialog_on_stderr_match for "unrecognized disk label" when checking
|
||||||
success_check, stdout_check, stderr_check = self._execute_shell_command(
|
success_check, stdout_check, stderr_check = self._execute_shell_command(
|
||||||
["parted", "-s", disk_path, "print"],
|
["parted", "-s", disk_path, "print"],
|
||||||
f"检查磁盘 {disk_path} 分区表失败",
|
f"检查磁盘 {disk_path} 分区表失败",
|
||||||
suppress_critical_dialog_on_stderr_match=("无法辨识的磁盘卷标", "unrecognized disk label"),
|
suppress_critical_dialog_on_stderr_match=("无法辨识的磁盘卷标", "unrecognized disk label"),
|
||||||
root_privilege=True
|
root_privilege=True
|
||||||
)
|
)
|
||||||
if success_check:
|
# If success_check is False, it means _execute_shell_command encountered an error.
|
||||||
|
# If that error was "unrecognized disk label", it was suppressed, and we treat it as no partition table.
|
||||||
|
# If it was another error, a dialog was shown by _execute_shell_command, and we should stop.
|
||||||
|
if not success_check:
|
||||||
|
# Check if the failure was due to "unrecognized disk label" (which means no partition table)
|
||||||
|
if any(s in stderr_check for s in ("无法辨识的磁盘卷标", "unrecognized disk label")):
|
||||||
|
logger.info(f"磁盘 {disk_path} 没有可识别的分区表。")
|
||||||
|
has_partition_table = False # Explicitly set to False
|
||||||
|
else:
|
||||||
|
logger.error(f"检查磁盘 {disk_path} 分区表时发生非预期的错误: {stderr_check}")
|
||||||
|
return False # Other critical error, stop operation.
|
||||||
|
else: # success_check is True
|
||||||
if "Partition Table: unknown" not in stdout_check and "分区表:unknown" not in stdout_check:
|
if "Partition Table: unknown" not in stdout_check and "分区表:unknown" not in stdout_check:
|
||||||
has_partition_table = True
|
has_partition_table = True
|
||||||
else:
|
else:
|
||||||
logger.info(f"parted print 报告磁盘 {disk_path} 的分区表为 'unknown'。")
|
logger.info(f"parted print 报告磁盘 {disk_path} 的分区表为 'unknown'。")
|
||||||
else:
|
has_partition_table = False
|
||||||
logger.info(f"parted print 命令失败,但可能不是因为没有分区表,错误信息: {stderr_check}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"检查磁盘 {disk_path} 分区表时发生异常: {e}")
|
|
||||||
pass
|
|
||||||
|
|
||||||
actual_start_mib_for_parted = 0.0
|
actual_start_mib_for_parted = 0.0
|
||||||
|
|
||||||
@@ -374,11 +408,11 @@ class DiskOperations:
|
|||||||
create_cmd,
|
create_cmd,
|
||||||
f"在 {disk_path} 上创建分区失败"
|
f"在 {disk_path} 上创建分区失败"
|
||||||
)
|
)
|
||||||
if not success:
|
if success:
|
||||||
return False
|
|
||||||
|
|
||||||
QMessageBox.information(None, "成功", f"在 {disk_path} 上成功创建了 {size_for_log} 的分区。")
|
QMessageBox.information(None, "成功", f"在 {disk_path} 上成功创建了 {size_for_log} 的分区。")
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def delete_partition(self, device_path):
|
def delete_partition(self, device_path):
|
||||||
"""
|
"""
|
||||||
@@ -394,9 +428,12 @@ class DiskOperations:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# 尝试卸载分区
|
# 尝试卸载分区
|
||||||
self.unmount_partition(device_path, show_dialog_on_error=False) # 静默卸载
|
# The unmount_partition method now handles "not mounted" gracefully without dialog and returns True.
|
||||||
|
# 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.
|
||||||
self._remove_fstab_entry(device_path)
|
self._remove_fstab_entry(device_path)
|
||||||
|
|
||||||
# 获取父磁盘和分区号
|
# 获取父磁盘和分区号
|
||||||
@@ -472,4 +509,3 @@ class DiskOperations:
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
20
form.ui
20
form.ui
@@ -14,7 +14,14 @@
|
|||||||
<string>Linux 存储管理工具</string>
|
<string>Linux 存储管理工具</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,4,1">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="refreshButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>刷新数据</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
@@ -24,7 +31,7 @@
|
|||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>块设备概览</string>
|
<string>块设备概览</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeWidget" name="treeWidget_block_devices">
|
<widget class="QTreeWidget" name="treeWidget_block_devices">
|
||||||
<column>
|
<column>
|
||||||
@@ -130,13 +137,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="refreshButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>刷新数据</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTextEdit" name="logOutputTextEdit">
|
<widget class="QTextEdit" name="logOutputTextEdit">
|
||||||
<property name="readOnly">
|
<property name="readOnly">
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1000</width>
|
<width>1000</width>
|
||||||
<height>23</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 初始化管理器和操作类
|
# 初始化管理器和操作类
|
||||||
self.system_manager = SystemInfoManager()
|
self.system_manager = SystemInfoManager()
|
||||||
self.disk_ops = DiskOperations()
|
self.disk_ops = DiskOperations(self.system_manager)
|
||||||
self.raid_ops = RaidOperations()
|
self.raid_ops = RaidOperations()
|
||||||
self.lvm_ops = LvmOperations()
|
self.lvm_ops = LvmOperations()
|
||||||
|
|
||||||
@@ -208,12 +208,12 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
if dev_data.get('children'):
|
if dev_data.get('children'):
|
||||||
logger.debug(f"磁盘 {disk_path} 存在现有分区,尝试计算下一个分区起始位置。")
|
logger.debug(f"磁盘 {disk_path} 存在现有分区,尝试计算下一个分区起始位置。")
|
||||||
calculated_start_mib = self.disk_ops.get_disk_next_partition_start_mib(disk_path)
|
calculated_start_mib, largest_free_space_mib = self.disk_ops.get_disk_free_space_info_mib(disk_path, total_disk_mib)
|
||||||
if calculated_start_mib is None:
|
if calculated_start_mib is None or largest_free_space_mib is None:
|
||||||
QMessageBox.critical(self, "错误", f"无法确定磁盘 {disk_path} 的分区起始位置。")
|
QMessageBox.critical(self, "错误", f"无法确定磁盘 {disk_path} 的分区起始位置。")
|
||||||
return
|
return
|
||||||
start_position_mib = calculated_start_mib
|
start_position_mib = calculated_start_mib
|
||||||
max_available_mib = total_disk_mib - start_position_mib
|
max_available_mib = largest_free_space_mib
|
||||||
if max_available_mib < 0:
|
if max_available_mib < 0:
|
||||||
max_available_mib = 0.0
|
max_available_mib = 0.0
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -39,17 +39,18 @@ class RaidOperations:
|
|||||||
|
|
||||||
return True, stdout, stderr
|
return True, stdout, stderr
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
stderr_output = e.stderr.strip() # 获取标准错误输出
|
||||||
logger.error(f"{error_msg_prefix} 命令: {full_cmd_str}")
|
logger.error(f"{error_msg_prefix} 命令: {full_cmd_str}")
|
||||||
logger.error(f"退出码: {e.returncode}")
|
logger.error(f"退出码: {e.returncode}")
|
||||||
logger.error(f"标准输出: {e.stdout.strip()}")
|
logger.error(f"标准输出: {e.stdout.strip()}")
|
||||||
logger.error(f"标准错误: {e.stderr.strip()}")
|
logger.error(f"标准错误: {stderr_output}")
|
||||||
|
|
||||||
# 根据 suppress_critical_dialog_on_stderr_match 参数决定是否弹出错误对话框
|
# 根据 suppress_critical_dialog_on_stderr_match 参数决定是否弹出错误对话框
|
||||||
if suppress_critical_dialog_on_stderr_match and suppress_critical_dialog_on_stderr_match in e.stderr.strip():
|
if suppress_critical_dialog_on_stderr_match and suppress_critical_dialog_on_stderr_match in stderr_output:
|
||||||
logger.info(f"特定错误 '{suppress_critical_dialog_on_stderr_match}' 匹配,已抑制错误对话框。")
|
logger.info(f"特定错误 '{suppress_critical_dialog_on_stderr_match}' 匹配,已抑制错误对话框。")
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(None, "错误", f"{error_msg_prefix}\n详细信息: {e.stderr.strip()}")
|
QMessageBox.critical(None, "错误", f"{error_msg_prefix}\n详细信息: {stderr_output}")
|
||||||
return False, e.stdout, e.stderr
|
return False, e.stdout, stderr_output # 返回 stderr_output
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error(f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
logger.error(f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
||||||
QMessageBox.critical(None, "错误", f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
QMessageBox.critical(None, "错误", f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
||||||
@@ -59,13 +60,50 @@ class RaidOperations:
|
|||||||
QMessageBox.critical(None, "错误", f"{error_msg_prefix}\n未知错误: {e}")
|
QMessageBox.critical(None, "错误", f"{error_msg_prefix}\n未知错误: {e}")
|
||||||
return False, "", str(e)
|
return False, "", str(e)
|
||||||
|
|
||||||
|
def _get_next_available_md_device_name(self):
|
||||||
|
"""
|
||||||
|
查找下一个可用的 /dev/mdX 设备名称(例如 /dev/md0, /dev/md1, ...)。
|
||||||
|
"""
|
||||||
|
existing_md_numbers = set()
|
||||||
|
try:
|
||||||
|
raid_arrays = self.system_manager.get_mdadm_arrays()
|
||||||
|
for array in raid_arrays:
|
||||||
|
device_path = array.get('device')
|
||||||
|
if device_path and device_path.startswith('/dev/md'):
|
||||||
|
# 匹配 /dev/mdX 形式的设备名
|
||||||
|
match = re.match(r'/dev/md(\d+)', device_path)
|
||||||
|
if match:
|
||||||
|
existing_md_numbers.add(int(match.group(1)))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"获取现有 RAID 阵列信息失败,可能无法找到最优的下一个设备名: {e}")
|
||||||
|
|
||||||
|
next_md_num = 0
|
||||||
|
while next_md_num in existing_md_numbers:
|
||||||
|
next_md_num += 1
|
||||||
|
|
||||||
|
return f"/dev/md{next_md_num}"
|
||||||
|
|
||||||
def create_raid_array(self, devices, level, chunk_size):
|
def create_raid_array(self, devices, level, chunk_size):
|
||||||
"""
|
"""
|
||||||
创建 RAID 阵列。
|
创建 RAID 阵列。
|
||||||
:param devices: 成员设备列表,例如 ['/dev/sdb1', '/dev/sdc1']
|
:param devices: 成员设备列表,例如 ['/dev/sdb1', '/dev/sdc1']
|
||||||
:param level: RAID 级别,例如 'raid0', 'raid1', 'raid5'
|
:param level: RAID 级别,例如 'raid0', 'raid1', 'raid5' (也可以是整数 0, 1, 5)
|
||||||
:param chunk_size: Chunk 大小 (KB)
|
:param chunk_size: Chunk 大小 (KB)
|
||||||
"""
|
"""
|
||||||
|
# --- 标准化 RAID 级别输入 ---
|
||||||
|
if isinstance(level, int):
|
||||||
|
level_str = f"raid{level}"
|
||||||
|
elif isinstance(level, str):
|
||||||
|
if level in ["0", "1", "5", "6", "10"]: # 增加 "6", "10" 确保全面
|
||||||
|
level_str = f"raid{level}"
|
||||||
|
else:
|
||||||
|
level_str = level
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(None, "错误", f"不支持的 RAID 级别类型: {type(level)}")
|
||||||
|
return False
|
||||||
|
level = level_str
|
||||||
|
# --- 标准化 RAID 级别输入结束 ---
|
||||||
|
|
||||||
if not devices or len(devices) < 2:
|
if not devices or len(devices) < 2:
|
||||||
QMessageBox.critical(None, "错误", "创建 RAID 阵列至少需要两个成员设备。")
|
QMessageBox.critical(None, "错误", "创建 RAID 阵列至少需要两个成员设备。")
|
||||||
return False
|
return False
|
||||||
@@ -74,6 +112,17 @@ class RaidOperations:
|
|||||||
if level == "raid5" and len(devices) < 3:
|
if level == "raid5" and len(devices) < 3:
|
||||||
QMessageBox.critical(None, "错误", "RAID5 至少需要三个设备。")
|
QMessageBox.critical(None, "错误", "RAID5 至少需要三个设备。")
|
||||||
return False
|
return False
|
||||||
|
# 补充其他 RAID 级别设备数量检查,与 dialogs.py 保持一致
|
||||||
|
if level == "raid1" and len(devices) < 2:
|
||||||
|
QMessageBox.critical(None, "错误", "RAID1 至少需要两个设备。")
|
||||||
|
return False
|
||||||
|
if level == "raid6" and len(devices) < 4:
|
||||||
|
QMessageBox.critical(None, "错误", "RAID6 至少需要四个设备。")
|
||||||
|
return False
|
||||||
|
if level == "raid10" and len(devices) < 2:
|
||||||
|
QMessageBox.critical(None, "错误", "RAID10 至少需要两个设备。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# 确认操作
|
# 确认操作
|
||||||
reply = QMessageBox.question(None, "确认创建 RAID 阵列",
|
reply = QMessageBox.question(None, "确认创建 RAID 阵列",
|
||||||
@@ -90,69 +139,80 @@ class RaidOperations:
|
|||||||
for dev in devices:
|
for dev in devices:
|
||||||
# 使用 --force 选项,避免交互式提示
|
# 使用 --force 选项,避免交互式提示
|
||||||
clear_cmd = ["mdadm", "--zero-superblock", "--force", dev]
|
clear_cmd = ["mdadm", "--zero-superblock", "--force", dev]
|
||||||
|
|
||||||
|
# 定义表示设备上没有超级块的错误信息,根据日志调整
|
||||||
|
no_superblock_error_match = "Unrecognised md component device"
|
||||||
|
|
||||||
success, _, stderr = self._execute_shell_command(
|
success, _, stderr = self._execute_shell_command(
|
||||||
clear_cmd,
|
clear_cmd,
|
||||||
f"清除设备 {dev} 上的旧 RAID 超级块失败",
|
f"清除设备 {dev} 上的旧 RAID 超级块失败",
|
||||||
suppress_critical_dialog_on_stderr_match="No superblocks" # 抑制“没有超级块”的错误提示
|
suppress_critical_dialog_on_stderr_match=no_superblock_error_match
|
||||||
)
|
)
|
||||||
if success:
|
|
||||||
|
# 检查 stderr 是否包含“未识别的 MD 组件设备”信息
|
||||||
|
if no_superblock_error_match in stderr:
|
||||||
|
logger.info(f"设备 {dev} 上未找到旧 RAID 超级块,已确保其干净。")
|
||||||
|
elif success:
|
||||||
logger.info(f"已清除设备 {dev} 上的旧 RAID 超级块。")
|
logger.info(f"已清除设备 {dev} 上的旧 RAID 超级块。")
|
||||||
else:
|
else:
|
||||||
# 如果清除失败,但不是因为“没有超级块”,则可能需要进一步处理
|
logger.error(f"清除设备 {dev} 上的旧 RAID 超级块失败,中断 RAID 创建。")
|
||||||
if "No superblocks" not in stderr:
|
return False
|
||||||
QMessageBox.warning(None, "警告", f"清除设备 {dev} 上的旧 RAID 超级块可能失败,但尝试继续。")
|
|
||||||
|
|
||||||
# 2. 创建 RAID 阵列
|
# 2. 创建 RAID 阵列
|
||||||
# 默认阵列名为 /dev/md/new_raid,可以考虑让用户输入
|
# --- 修改点:自动生成唯一的阵列名称 ---
|
||||||
array_name = "/dev/md/new_raid"
|
array_name = self._get_next_available_md_device_name()
|
||||||
|
logger.info(f"将使用自动生成的 RAID 阵列名称: {array_name}")
|
||||||
|
# --- 修改点结束 ---
|
||||||
|
|
||||||
if level == "raid0":
|
if level == "raid0":
|
||||||
# RAID0 至少2个设备
|
|
||||||
create_cmd = ["mdadm", "--create", array_name, "--level=raid0",
|
create_cmd = ["mdadm", "--create", array_name, "--level=raid0",
|
||||||
f"--raid-devices={len(devices)}", f"--chunk={chunk_size}K"] + devices
|
f"--raid-devices={len(devices)}", f"--chunk={chunk_size}K"] + devices
|
||||||
elif level == "raid1":
|
elif level == "raid1":
|
||||||
# RAID1 至少2个设备
|
|
||||||
create_cmd = ["mdadm", "--create", array_name, "--level=raid1",
|
create_cmd = ["mdadm", "--create", array_name, "--level=raid1",
|
||||||
f"--raid-devices={len(devices)}"] + devices
|
f"--raid-devices={len(devices)}"] + devices
|
||||||
elif level == "raid5":
|
elif level == "raid5":
|
||||||
# RAID5 至少3个设备
|
|
||||||
create_cmd = ["mdadm", "--create", array_name, "--level=raid5",
|
create_cmd = ["mdadm", "--create", array_name, "--level=raid5",
|
||||||
f"--raid-devices={len(devices)}"] + devices
|
f"--raid-devices={len(devices)}"] + devices
|
||||||
|
elif level == "raid6": # 增加 RAID6
|
||||||
|
create_cmd = ["mdadm", "--create", array_name, "--level=raid6",
|
||||||
|
f"--raid-devices={len(devices)}", f"--chunk={chunk_size}K"] + devices
|
||||||
|
elif level == "raid10": # 增加 RAID10
|
||||||
|
create_cmd = ["mdadm", "--create", array_name, "--level=raid10",
|
||||||
|
f"--raid-devices={len(devices)}", f"--chunk={chunk_size}K"] + devices
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(None, "错误", f"不支持的 RAID 级别: {level}")
|
QMessageBox.critical(None, "错误", f"不支持的 RAID 级别: {level}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 在 --create 命令中添加 --force 选项
|
# 在 --create 命令中添加 --force 选项
|
||||||
create_cmd.insert(2, "--force") # 插入到 --create 后面
|
create_cmd.insert(2, "--force")
|
||||||
|
|
||||||
# <--- 关键修改:通过 input_to_command 参数传入 'y\n' 来强制 mdadm 接受
|
success_create, stdout_create, stderr_create = self._execute_shell_command(
|
||||||
if not self._execute_shell_command(create_cmd, f"创建 RAID 阵列失败", input_to_command='y\n')[0]:
|
create_cmd,
|
||||||
|
f"创建 RAID 阵列失败",
|
||||||
|
input_to_command='y\n' # 尝试通过 stdin 传递 'y'
|
||||||
|
)
|
||||||
|
if not success_create:
|
||||||
|
# 检查是否是由于 "Array name ... is in use already." 导致的失败
|
||||||
|
if "Array name" in stderr_create and "is in use already" in stderr_create:
|
||||||
|
QMessageBox.critical(None, "错误", f"创建 RAID 阵列失败:阵列名称 {array_name} 已被占用。请尝试停止或删除现有阵列。")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"成功创建 RAID {level} 阵列 {array_name}。")
|
logger.info(f"成功创建 RAID {level} 阵列 {array_name}。")
|
||||||
QMessageBox.information(None, "成功", f"成功创建 RAID {level} 阵列 {array_name}。")
|
QMessageBox.information(None, "成功", f"成功创建 RAID {level} 阵列 {array_name}。")
|
||||||
|
|
||||||
# 3. 刷新 mdadm 配置并等待阵列激活
|
# 3. 刷新 mdadm 配置并等待阵列激活
|
||||||
# 注意:这里使用 '>>' 重定向,subprocess.run 无法直接处理 shell 重定向符号
|
|
||||||
# 需要改成先读取,再写入,或者使用 bash -c "..."
|
|
||||||
# 暂时先用 bash -c 的方式,更简单
|
|
||||||
# 旧代码:self._execute_shell_command(["mdadm", "--examine", "--scan", ">>", "/etc/mdadm/mdadm.conf"], "更新 mdadm.conf 失败")
|
|
||||||
|
|
||||||
# 获取新的 mdadm.conf 内容
|
|
||||||
examine_scan_cmd = ["mdadm", "--examine", "--scan"]
|
examine_scan_cmd = ["mdadm", "--examine", "--scan"]
|
||||||
success_scan, scan_stdout, _ = self._execute_shell_command(examine_scan_cmd, "扫描 mdadm 配置失败")
|
success_scan, scan_stdout, _ = self._execute_shell_command(examine_scan_cmd, "扫描 mdadm 配置失败")
|
||||||
if success_scan:
|
if success_scan:
|
||||||
# 将扫描结果追加到 mdadm.conf
|
|
||||||
append_to_conf_cmd = ["bash", "-c", f"echo '{scan_stdout.strip()}' >> /etc/mdadm/mdadm.conf"]
|
append_to_conf_cmd = ["bash", "-c", f"echo '{scan_stdout.strip()}' >> /etc/mdadm/mdadm.conf"]
|
||||||
if not self._execute_shell_command(append_to_conf_cmd, "更新 /etc/mdadm/mdadm.conf 失败")[0]:
|
if not self._execute_shell_command(append_to_conf_cmd, "更新 /etc/mdadm/mdadm.conf 失败")[0]:
|
||||||
logger.warning("更新 /etc/mdadm/mdadm.conf 失败。")
|
logger.warning("更新 /etc/mdadm/mdadm.conf 失败。")
|
||||||
else:
|
else:
|
||||||
logger.warning("未能扫描到 mdadm 配置,跳过更新 mdadm.conf。")
|
logger.warning("未能扫描到 mdadm 配置,跳过更新 mdadm.conf。")
|
||||||
|
|
||||||
|
|
||||||
# self._execute_shell_command(["update-initramfs", "-u"], "更新 initramfs 失败")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def stop_raid_array(self, array_path):
|
def stop_raid_array(self, array_path):
|
||||||
"""
|
"""
|
||||||
停止一个 RAID 阵列。
|
停止一个 RAID 阵列。
|
||||||
@@ -173,7 +233,9 @@ class RaidOperations:
|
|||||||
# 暂时先直接调用 umount 命令,不处理 fstab
|
# 暂时先直接调用 umount 命令,不处理 fstab
|
||||||
# 或者,更好的方式是让 MainWindow 调用 disk_ops.unmount_partition
|
# 或者,更好的方式是让 MainWindow 调用 disk_ops.unmount_partition
|
||||||
# 此处简化处理,只执行 umount 命令
|
# 此处简化处理,只执行 umount 命令
|
||||||
self._execute_shell_command(["umount", array_path], f"尝试卸载 {array_path} 失败", suppress_critical_dialog_on_stderr_match="not mounted")
|
# 这里的 suppress_critical_dialog_on_stderr_match 应该与 DiskOperations 中的定义保持一致
|
||||||
|
self._execute_shell_command(["umount", array_path], f"尝试卸载 {array_path} 失败",
|
||||||
|
suppress_critical_dialog_on_stderr_match="not mounted") # 仅抑制英文,因为这里没有访问 DiskOperations 的 already_unmounted_errors
|
||||||
|
|
||||||
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
|
||||||
@@ -209,9 +271,24 @@ class RaidOperations:
|
|||||||
for dev in member_devices:
|
for dev in member_devices:
|
||||||
# 使用 --force 选项,避免交互式提示
|
# 使用 --force 选项,避免交互式提示
|
||||||
clear_cmd = ["mdadm", "--zero-superblock", "--force", dev]
|
clear_cmd = ["mdadm", "--zero-superblock", "--force", dev]
|
||||||
if not self._execute_shell_command(clear_cmd, f"清除设备 {dev} 上的 RAID 超级块失败")[0]:
|
|
||||||
|
# 定义表示设备上没有超级块的错误信息,根据日志调整
|
||||||
|
no_superblock_error_match = "Unrecognised md component device"
|
||||||
|
|
||||||
|
success, _, stderr = self._execute_shell_command(
|
||||||
|
clear_cmd,
|
||||||
|
f"清除设备 {dev} 上的 RAID 超级块失败",
|
||||||
|
suppress_critical_dialog_on_stderr_match=no_superblock_error_match
|
||||||
|
)
|
||||||
|
|
||||||
|
if no_superblock_error_match in stderr:
|
||||||
|
logger.info(f"设备 {dev} 上未找到旧 RAID 超级块,已确保其干净。")
|
||||||
|
elif success:
|
||||||
|
logger.info(f"已清除设备 {dev} 上的旧 RAID 超级块。")
|
||||||
|
else:
|
||||||
success_all_cleared = False
|
success_all_cleared = False
|
||||||
QMessageBox.warning(None, "警告", f"未能清除设备 {dev} 上的 RAID 超级块,请手动检查。")
|
# 错误对话框已由 _execute_shell_command 弹出,这里只记录警告
|
||||||
|
logger.warning(f"未能清除设备 {dev} 上的 RAID 超级块,请手动检查。")
|
||||||
|
|
||||||
if success_all_cleared:
|
if success_all_cleared:
|
||||||
logger.info(f"成功删除 RAID 阵列 {array_path} 并清除了成员设备超级块。")
|
logger.info(f"成功删除 RAID 阵列 {array_path} 并清除了成员设备超级块。")
|
||||||
|
|||||||
218
system_info.py
218
system_info.py
@@ -3,7 +3,7 @@ import subprocess
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import os # 导入 os 模块
|
import os
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ class SystemInfoManager:
|
|||||||
Helper to find device data by its path recursively.
|
Helper to find device data by its path recursively.
|
||||||
Added type check for target_path.
|
Added type check for target_path.
|
||||||
"""
|
"""
|
||||||
if not isinstance(target_path, str): # 添加类型检查
|
if not isinstance(target_path, str):
|
||||||
logger.warning(f"传入 _find_device_by_path_recursive 的 target_path 不是字符串: {target_path} (类型: {type(target_path)})")
|
logger.warning(f"传入 _find_device_by_path_recursive 的 target_path 不是字符串: {target_path} (类型: {type(target_path)})")
|
||||||
return None
|
return None
|
||||||
for dev in dev_list:
|
for dev in dev_list:
|
||||||
@@ -94,42 +94,109 @@ class SystemInfoManager:
|
|||||||
return found
|
return found
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _find_device_by_maj_min_recursive(self, dev_list, target_maj_min):
|
||||||
|
"""
|
||||||
|
Helper to find device data by its major:minor number recursively.
|
||||||
|
"""
|
||||||
|
if not isinstance(target_maj_min, str):
|
||||||
|
logger.warning(f"传入 _find_device_by_maj_min_recursive 的 target_maj_min 不是字符串: {target_maj_min} (类型: {type(target_maj_min)})")
|
||||||
|
return None
|
||||||
|
for dev in dev_list:
|
||||||
|
if dev.get('maj:min') == target_maj_min:
|
||||||
|
return dev
|
||||||
|
if 'children' in dev:
|
||||||
|
found = self._find_device_by_maj_min_recursive(dev['children'], target_maj_min)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
def get_mountpoint_for_device(self, device_path):
|
def get_mountpoint_for_device(self, device_path):
|
||||||
"""
|
"""
|
||||||
根据设备路径获取其挂载点。
|
根据设备路径获取其挂载点。
|
||||||
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名。
|
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名,以及 LVM 逻辑卷的符号链接。
|
||||||
Added type check for device_path.
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(device_path, str): # 添加类型检查
|
if not isinstance(device_path, str):
|
||||||
logger.warning(f"传入 get_mountpoint_for_device 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
|
logger.warning(f"传入 get_mountpoint_for_device 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
original_device_path = device_path
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 正在处理原始设备路径: {original_device_path}")
|
||||||
|
|
||||||
devices = self.get_block_devices()
|
devices = self.get_block_devices()
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 从 lsblk 获取到 {len(devices)} 个块设备信息。")
|
||||||
|
|
||||||
|
target_maj_min = None
|
||||||
|
current_search_path = original_device_path
|
||||||
|
|
||||||
|
# 1. 解析符号链接以获取实际路径,并尝试获取 maj:min
|
||||||
|
if original_device_path.startswith('/dev/') and os.path.exists(original_device_path):
|
||||||
|
try:
|
||||||
|
# 先尝试解析真实路径,因为 stat 可能对符号链接返回链接本身的 maj:min
|
||||||
|
resolved_path = os.path.realpath(original_device_path)
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 原始设备路径 {original_device_path} 解析为 {resolved_path}")
|
||||||
|
current_search_path = resolved_path # 使用解析后的路径进行 stat 和后续查找
|
||||||
|
|
||||||
|
# 获取解析后路径的 maj:min
|
||||||
|
stat_output, _ = self._run_command(["stat", "-c", "%t:%T", resolved_path], check_output=True)
|
||||||
|
raw_maj_min = stat_output.strip() # e.g., "fc:0"
|
||||||
|
|
||||||
|
# MODIFIED: Convert hex major to decimal
|
||||||
|
if ':' in raw_maj_min:
|
||||||
|
major_hex, minor_dec = raw_maj_min.split(':')
|
||||||
|
try:
|
||||||
|
major_dec = str(int(major_hex, 16)) # Convert hex to decimal string
|
||||||
|
target_maj_min = f"{major_dec}:{minor_dec}" # e.g., "252:0"
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 解析后设备 {resolved_path} 的 maj:min (hex) 为 {raw_maj_min},转换为 decimal 为 {target_maj_min}")
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"get_mountpoint_for_device: 无法将 maj:min 的主要部分 '{major_hex}' 从十六进制转换为十进制。")
|
||||||
|
target_maj_min = None # Fallback if conversion fails
|
||||||
|
else:
|
||||||
|
logger.warning(f"get_mountpoint_for_device: stat 输出 '{raw_maj_min}' 格式不符合预期。")
|
||||||
|
target_maj_min = None
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 无法获取 {resolved_path} 的 maj:min (命令失败): {e.stderr.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 无法获取 {resolved_path} 的 maj:min (未知错误): {e}")
|
||||||
|
elif original_device_path.startswith('/dev/') and not os.path.exists(original_device_path):
|
||||||
|
logger.warning(f"get_mountpoint_for_device: 设备路径 {original_device_path} 不存在,无法获取挂载点。")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 2. 尝试使用 (可能已解析的) current_search_path 在 lsblk 输出中查找
|
||||||
|
dev_info = self._find_device_by_path_recursive(devices, current_search_path)
|
||||||
|
logger.debug(f"get_mountpoint_for_device: _find_device_by_path_recursive 查找结果 (使用 {current_search_path}): {dev_info}")
|
||||||
|
|
||||||
# 1. 尝试直接使用提供的 device_path 在 lsblk 输出中查找
|
|
||||||
dev_info = self._find_device_by_path_recursive(devices, device_path)
|
|
||||||
if dev_info:
|
if dev_info:
|
||||||
logger.debug(f"直接从 lsblk 获取到 {device_path} 的挂载点: {dev_info.get('mountpoint')}")
|
mountpoint = dev_info.get('mountpoint')
|
||||||
return dev_info.get('mountpoint')
|
logger.debug(f"get_mountpoint_for_device: 直接从 lsblk 获取到 {original_device_path} (解析为 {current_search_path}) 的挂载点: {mountpoint}")
|
||||||
|
return mountpoint
|
||||||
|
|
||||||
# 2. 如果直接查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid)
|
# 3. 如果直接路径查找失败,并且我们有 maj:min,尝试通过 maj:min 查找
|
||||||
if device_path.startswith('/dev/md'): # 此处 device_path 已通过类型检查
|
if target_maj_min:
|
||||||
logger.debug(f"处理 RAID 阵列 {device_path} 以获取挂载点...")
|
logger.debug(f"get_mountpoint_for_device: 直接路径查找失败,尝试通过 maj:min ({target_maj_min}) 查找。")
|
||||||
|
dev_info_by_maj_min = self._find_device_by_maj_min_recursive(devices, target_maj_min)
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 通过 maj:min 查找结果: {dev_info_by_maj_min}")
|
||||||
|
if dev_info_by_maj_min:
|
||||||
|
mountpoint = dev_info_by_maj_min.get('mountpoint')
|
||||||
|
logger.debug(f"get_mountpoint_for_device: 通过 maj:min 找到 {original_device_path} 的挂载点: {mountpoint}")
|
||||||
|
return mountpoint
|
||||||
|
|
||||||
# 获取 RAID 阵列的实际内核设备路径(例如 /dev/md127)
|
# 4. 如果仍然查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid)
|
||||||
actual_md_device_path = self._get_actual_md_device_path(device_path)
|
if original_device_path.startswith('/dev/md'):
|
||||||
logger.debug(f"RAID 阵列 {device_path} 的实际设备路径: {actual_md_device_path}")
|
logger.debug(f"get_mountpoint_for_device: 正在处理 RAID 阵列 {original_device_path} 以获取挂载点...")
|
||||||
|
actual_md_device_path = self._get_actual_md_device_path(original_device_path)
|
||||||
|
logger.debug(f"get_mountpoint_for_device: RAID 阵列 {original_device_path} 的实际设备路径: {actual_md_device_path}")
|
||||||
|
|
||||||
if actual_md_device_path:
|
if actual_md_device_path:
|
||||||
# 现在,使用实际的内核设备路径从 lsblk 中查找挂载点
|
|
||||||
actual_dev_info = self._find_device_by_path_recursive(devices, actual_md_device_path)
|
actual_dev_info = self._find_device_by_path_recursive(devices, actual_md_device_path)
|
||||||
logger.debug(f"实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_dev_info}")
|
logger.debug(f"get_mountpoint_for_device: 实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_dev_info}")
|
||||||
|
|
||||||
if actual_dev_info:
|
if actual_dev_info:
|
||||||
logger.debug(f"在实际设备 {actual_md_device_path} 上找到了挂载点: {actual_dev_info.get('mountpoint')}")
|
mountpoint = actual_dev_info.get('mountpoint')
|
||||||
return actual_dev_info.get('mountpoint')
|
logger.debug(f"get_mountpoint_for_device: 在实际设备 {actual_md_device_path} 上找到了挂载点: {mountpoint}")
|
||||||
|
return mountpoint
|
||||||
|
|
||||||
logger.debug(f"未能获取到 {device_path} 的挂载点。")
|
logger.debug(f"get_mountpoint_for_device: 未能获取到 {original_device_path} 的挂载点。")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_mdadm_arrays(self):
|
def get_mdadm_arrays(self):
|
||||||
@@ -269,9 +336,10 @@ class SystemInfoManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取LVM卷组信息失败: {e}")
|
logger.error(f"获取LVM卷组信息失败: {e}")
|
||||||
|
|
||||||
# Get LVs
|
# Get LVs (MODIFIED: added -o lv_path)
|
||||||
try:
|
try:
|
||||||
stdout, _ = self._run_command(["lvs", "--reportformat", "json"])
|
# 明确请求 lv_path,因为默认的 --reportformat json 不包含它
|
||||||
|
stdout, _ = self._run_command(["lvs", "-o", "lv_name,vg_name,lv_uuid,lv_size,lv_attr,origin,snap_percent,lv_path", "--reportformat", "json"])
|
||||||
data = json.loads(stdout)
|
data = json.loads(stdout)
|
||||||
if 'report' in data and data['report']:
|
if 'report' in data and data['report']:
|
||||||
for lv_data in data['report'][0].get('lv', []):
|
for lv_data in data['report'][0].get('lv', []):
|
||||||
@@ -283,7 +351,7 @@ class SystemInfoManager:
|
|||||||
'lv_attr': lv_data.get('lv_attr'),
|
'lv_attr': lv_data.get('lv_attr'),
|
||||||
'origin': lv_data.get('origin'),
|
'origin': lv_data.get('origin'),
|
||||||
'snap_percent': lv_data.get('snap_percent'),
|
'snap_percent': lv_data.get('snap_percent'),
|
||||||
'lv_path': lv_data.get('lv_path')
|
'lv_path': lv_data.get('lv_path') # 现在应该能正确获取到路径了
|
||||||
})
|
})
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
if "No logical volume found" in e.stderr or "No logical volumes found" in e.stdout:
|
if "No logical volume found" in e.stderr or "No logical volumes found" in e.stdout:
|
||||||
@@ -358,7 +426,7 @@ class SystemInfoManager:
|
|||||||
通过检查 /dev/md/ 目录下的符号链接来获取。
|
通过检查 /dev/md/ 目录下的符号链接来获取。
|
||||||
Added type check for array_path.
|
Added type check for array_path.
|
||||||
"""
|
"""
|
||||||
if not isinstance(array_path, str): # 添加类型检查
|
if not isinstance(array_path, str):
|
||||||
logger.warning(f"传入 _get_actual_md_device_path 的 array_path 不是字符串: {array_path} (类型: {type(array_path)})")
|
logger.warning(f"传入 _get_actual_md_device_path 的 array_path 不是字符串: {array_path} (类型: {type(array_path)})")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -378,63 +446,103 @@ class SystemInfoManager:
|
|||||||
def get_device_details_by_path(self, device_path):
|
def get_device_details_by_path(self, device_path):
|
||||||
"""
|
"""
|
||||||
根据设备路径获取设备的 UUID 和文件系统类型 (fstype)。
|
根据设备路径获取设备的 UUID 和文件系统类型 (fstype)。
|
||||||
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名。
|
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名,以及 LVM 逻辑卷的符号链接。
|
||||||
Added type check for device_path.
|
Added type check for device_path.
|
||||||
:param device_path: 设备的路径,例如 '/dev/sdb1' 或 '/dev/md0' 或 '/dev/md/new_raid'
|
:param device_path: 设备的路径,例如 '/dev/sdb1' 或 '/dev/md0' 或 '/dev/md/new_raid'
|
||||||
:return: 包含 'uuid' 和 'fstype' 的字典,如果未找到则返回 None。
|
:return: 包含 'uuid' 和 'fstype' 的字典,如果未找到则返回 None。
|
||||||
"""
|
"""
|
||||||
if not isinstance(device_path, str): # 添加类型检查
|
if not isinstance(device_path, str):
|
||||||
logger.warning(f"传入 get_device_details_by_path 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
|
logger.warning(f"传入 get_device_details_by_path 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
original_device_path = device_path
|
||||||
|
logger.debug(f"get_device_details_by_path: 正在处理原始设备路径: {original_device_path}")
|
||||||
|
|
||||||
devices = self.get_block_devices() # 获取所有块设备信息
|
devices = self.get_block_devices() # 获取所有块设备信息
|
||||||
|
logger.debug(f"get_device_details_by_path: 从 lsblk 获取到 {len(devices)} 个块设备信息。")
|
||||||
|
|
||||||
# 1. 首先,尝试直接使用提供的 device_path 在 lsblk 输出中查找
|
target_maj_min = None
|
||||||
# 对于 /dev/md/new_raid 这样的别名,这一步通常会返回 None
|
current_search_path = original_device_path
|
||||||
lsblk_details = self._find_device_by_path_recursive(devices, device_path)
|
|
||||||
logger.debug(f"lsblk_details for {device_path} (direct lookup): {lsblk_details}")
|
|
||||||
|
|
||||||
if lsblk_details and lsblk_details.get('fstype'): # 即使没有UUID,有fstype也算找到部分信息
|
# 1. 解析符号链接以获取实际路径,并尝试获取 maj:min
|
||||||
# 如果直接找到了 fstype,就返回 lsblk 提供的 UUID 和 fstype
|
if original_device_path.startswith('/dev/') and os.path.exists(original_device_path):
|
||||||
# 这里的 UUID 应该是文件系统 UUID
|
try:
|
||||||
logger.debug(f"直接从 lsblk 获取到 {device_path} 的详情: {lsblk_details}")
|
resolved_path = os.path.realpath(original_device_path)
|
||||||
|
logger.debug(f"get_device_details_by_path: 原始设备路径 {original_device_path} 解析为 {resolved_path}")
|
||||||
|
current_search_path = resolved_path
|
||||||
|
|
||||||
|
stat_output, _ = self._run_command(["stat", "-c", "%t:%T", resolved_path], check_output=True)
|
||||||
|
raw_maj_min = stat_output.strip() # e.g., "fc:0"
|
||||||
|
|
||||||
|
# MODIFIED: Convert hex major to decimal
|
||||||
|
if ':' in raw_maj_min:
|
||||||
|
major_hex, minor_dec = raw_maj_min.split(':')
|
||||||
|
try:
|
||||||
|
major_dec = str(int(major_hex, 16)) # Convert hex to decimal string
|
||||||
|
target_maj_min = f"{major_dec}:{minor_dec}" # e.g., "252:0"
|
||||||
|
logger.debug(f"get_device_details_by_path: 解析后设备 {resolved_path} 的 maj:min (hex) 为 {raw_maj_min},转换为 decimal 为 {target_maj_min}")
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"get_device_details_by_path: 无法将 maj:min 的主要部分 '{major_hex}' 从十六进制转换为十进制。")
|
||||||
|
target_maj_min = None
|
||||||
|
else:
|
||||||
|
logger.warning(f"get_device_details_by_path: stat 输出 '{raw_maj_min}' 格式不符合预期。")
|
||||||
|
target_maj_min = None
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.debug(f"get_device_details_by_path: 无法获取 {resolved_path} 的 maj:min (命令失败): {e.stderr.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"get_device_details_by_path: 无法获取 {resolved_path} 的 maj:min (未知错误): {e}")
|
||||||
|
elif original_device_path.startswith('/dev/') and not os.path.exists(original_device_path):
|
||||||
|
logger.warning(f"get_device_details_by_path: 设备路径 {original_device_path} 不存在,无法获取详情。")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 2. 首先,尝试使用 (可能已解析的) current_search_path 在 lsblk 输出中查找
|
||||||
|
lsblk_details = self._find_device_by_path_recursive(devices, current_search_path)
|
||||||
|
logger.debug(f"get_device_details_by_path: _find_device_by_path_recursive 查找结果 (使用 {current_search_path}, 直接查找): {lsblk_details}")
|
||||||
|
|
||||||
|
if lsblk_details and lsblk_details.get('fstype'):
|
||||||
|
logger.debug(f"get_device_details_by_path: 直接从 lsblk 获取到 {original_device_path} (解析为 {current_search_path}) 的详情: {lsblk_details}")
|
||||||
return {
|
return {
|
||||||
'uuid': lsblk_details.get('uuid'),
|
'uuid': lsblk_details.get('uuid'),
|
||||||
'fstype': lsblk_details.get('fstype')
|
'fstype': lsblk_details.get('fstype')
|
||||||
}
|
}
|
||||||
|
|
||||||
# 2. 如果直接查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid)
|
# 3. 如果直接路径查找失败,并且我们有 maj:min,尝试通过 maj:min 查找
|
||||||
if device_path.startswith('/dev/md'): # 此处 device_path 已通过类型检查
|
if target_maj_min:
|
||||||
logger.debug(f"处理 RAID 阵列 {device_path}...")
|
logger.debug(f"get_device_details_by_path: 直接路径查找失败,尝试通过 maj:min ({target_maj_min}) 查找。")
|
||||||
|
dev_info_by_maj_min = self._find_device_by_maj_min_recursive(devices, target_maj_min)
|
||||||
|
logger.debug(f"get_device_details_by_path: 通过 maj:min 查找结果: {dev_info_by_maj_min}")
|
||||||
|
if dev_info_by_maj_min and dev_info_by_maj_min.get('fstype'):
|
||||||
|
logger.debug(f"get_device_details_by_path: 通过 maj:min 找到 {original_device_path} 的文件系统详情: {dev_info_by_maj_min}")
|
||||||
|
return {
|
||||||
|
'uuid': dev_info_by_maj_min.get('uuid'),
|
||||||
|
'fstype': dev_info_by_maj_min.get('fstype')
|
||||||
|
}
|
||||||
|
|
||||||
# 获取 RAID 阵列的实际内核设备路径(例如 /dev/md127)
|
# 4. 如果仍然没有找到,并且是 RAID 阵列(例如 /dev/md/new_raid)
|
||||||
actual_md_device_path = self._get_actual_md_device_path(device_path)
|
if original_device_path.startswith('/dev/md'):
|
||||||
logger.debug(f"RAID 阵列 {device_path} 的实际设备路径: {actual_md_device_path}")
|
logger.debug(f"get_device_details_by_path: 正在处理 RAID 阵列 {original_device_path}...")
|
||||||
|
|
||||||
|
actual_md_device_path = self._get_actual_md_device_path(original_device_path)
|
||||||
|
logger.debug(f"get_device_details_by_path: RAID 阵列 {original_device_path} 的实际设备路径: {actual_md_device_path}")
|
||||||
|
|
||||||
if actual_md_device_path:
|
if actual_md_device_path:
|
||||||
# 现在,使用实际的内核设备路径从 lsblk 中查找 fstype 和 UUID (文件系统 UUID)
|
|
||||||
actual_device_lsblk_details = self._find_device_by_path_recursive(devices, actual_md_device_path)
|
actual_device_lsblk_details = self._find_device_by_path_recursive(devices, actual_md_device_path)
|
||||||
logger.debug(f"实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_device_lsblk_details}")
|
logger.debug(f"get_device_details_by_path: 实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_device_lsblk_details}")
|
||||||
|
|
||||||
if actual_device_lsblk_details and actual_device_lsblk_details.get('fstype'):
|
if actual_device_lsblk_details and actual_device_lsblk_details.get('fstype'):
|
||||||
# 找到了实际 RAID 设备上的文件系统信息
|
logger.debug(f"get_device_details_by_path: 在实际设备 {actual_md_device_path} 上找到了文件系统详情: {actual_device_lsblk_details}")
|
||||||
# 此时的 UUID 是文件系统 UUID,fstype 是文件系统类型
|
|
||||||
logger.debug(f"在实际设备 {actual_md_device_path} 上找到了文件系统详情: {actual_device_lsblk_details}")
|
|
||||||
return {
|
return {
|
||||||
'uuid': actual_device_lsblk_details.get('uuid'),
|
'uuid': actual_device_lsblk_details.get('uuid'),
|
||||||
'fstype': actual_device_lsblk_details.get('fstype')
|
'fstype': actual_device_lsblk_details.get('fstype')
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# RAID 设备存在,但 lsblk 没有报告文件系统 (例如,尚未格式化)
|
logger.warning(f"get_device_details_by_path: RAID 阵列 {original_device_path} (实际设备 {actual_md_device_path}) 未找到文件系统类型。")
|
||||||
# 此时 fstype 为 None。如果需要,我们可以返回 RAID 阵列本身的 UUID,但 fstype 仍为 None
|
|
||||||
logger.warning(f"RAID 阵列 {device_path} (实际设备 {actual_md_device_path}) 未找到文件系统类型。")
|
|
||||||
# 对于 fstab,如果没有 fstype,就无法创建条目。
|
|
||||||
# 此时返回 None,让调用者知道无法写入 fstab。
|
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
logger.warning(f"无法确定 RAID 阵列 {device_path} 的实际内核设备路径。")
|
logger.warning(f"get_device_details_by_path: 无法确定 RAID 阵列 {original_device_path} 的实际内核设备路径。")
|
||||||
return None # 无法解析实际设备路径,也无法获取 fstype
|
return None
|
||||||
|
|
||||||
# 3. 如果仍然没有找到,返回 None
|
# 5. 如果仍然没有找到,返回 None
|
||||||
logger.debug(f"未能获取到 {device_path} 的任何详情。")
|
logger.debug(f"get_device_details_by_path: 未能获取到 {original_device_path} 的任何详情。")
|
||||||
return None
|
return None
|
||||||
|
|||||||
30
ui_form.py
30
ui_form.py
@@ -27,18 +27,23 @@ class Ui_MainWindow(object):
|
|||||||
MainWindow.resize(1000, 700)
|
MainWindow.resize(1000, 700)
|
||||||
self.centralwidget = QWidget(MainWindow)
|
self.centralwidget = QWidget(MainWindow)
|
||||||
self.centralwidget.setObjectName(u"centralwidget")
|
self.centralwidget.setObjectName(u"centralwidget")
|
||||||
self.verticalLayout = QVBoxLayout(self.centralwidget)
|
self.verticalLayout_2 = QVBoxLayout(self.centralwidget)
|
||||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||||
|
self.refreshButton = QPushButton(self.centralwidget)
|
||||||
|
self.refreshButton.setObjectName(u"refreshButton")
|
||||||
|
|
||||||
|
self.verticalLayout_2.addWidget(self.refreshButton)
|
||||||
|
|
||||||
self.tabWidget = QTabWidget(self.centralwidget)
|
self.tabWidget = QTabWidget(self.centralwidget)
|
||||||
self.tabWidget.setObjectName(u"tabWidget")
|
self.tabWidget.setObjectName(u"tabWidget")
|
||||||
self.tab_block_devices = QWidget()
|
self.tab_block_devices = QWidget()
|
||||||
self.tab_block_devices.setObjectName(u"tab_block_devices")
|
self.tab_block_devices.setObjectName(u"tab_block_devices")
|
||||||
self.verticalLayout_2 = QVBoxLayout(self.tab_block_devices)
|
self.verticalLayout = QVBoxLayout(self.tab_block_devices)
|
||||||
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
self.treeWidget_block_devices = QTreeWidget(self.tab_block_devices)
|
self.treeWidget_block_devices = QTreeWidget(self.tab_block_devices)
|
||||||
self.treeWidget_block_devices.setObjectName(u"treeWidget_block_devices")
|
self.treeWidget_block_devices.setObjectName(u"treeWidget_block_devices")
|
||||||
|
|
||||||
self.verticalLayout_2.addWidget(self.treeWidget_block_devices)
|
self.verticalLayout.addWidget(self.treeWidget_block_devices)
|
||||||
|
|
||||||
self.tabWidget.addTab(self.tab_block_devices, "")
|
self.tabWidget.addTab(self.tab_block_devices, "")
|
||||||
self.tab_raid = QWidget()
|
self.tab_raid = QWidget()
|
||||||
@@ -62,23 +67,20 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.tabWidget.addTab(self.tab_lvm, "")
|
self.tabWidget.addTab(self.tab_lvm, "")
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.tabWidget)
|
self.verticalLayout_2.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 = QTextEdit(self.centralwidget)
|
||||||
self.logOutputTextEdit.setObjectName(u"logOutputTextEdit")
|
self.logOutputTextEdit.setObjectName(u"logOutputTextEdit")
|
||||||
self.logOutputTextEdit.setReadOnly(True)
|
self.logOutputTextEdit.setReadOnly(True)
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.logOutputTextEdit)
|
self.verticalLayout_2.addWidget(self.logOutputTextEdit)
|
||||||
|
|
||||||
|
self.verticalLayout_2.setStretch(1, 4)
|
||||||
|
self.verticalLayout_2.setStretch(2, 1)
|
||||||
MainWindow.setCentralWidget(self.centralwidget)
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
self.menubar = QMenuBar(MainWindow)
|
self.menubar = QMenuBar(MainWindow)
|
||||||
self.menubar.setObjectName(u"menubar")
|
self.menubar.setObjectName(u"menubar")
|
||||||
self.menubar.setGeometry(QRect(0, 0, 1000, 23))
|
self.menubar.setGeometry(QRect(0, 0, 1000, 30))
|
||||||
MainWindow.setMenuBar(self.menubar)
|
MainWindow.setMenuBar(self.menubar)
|
||||||
self.statusbar = QStatusBar(MainWindow)
|
self.statusbar = QStatusBar(MainWindow)
|
||||||
self.statusbar.setObjectName(u"statusbar")
|
self.statusbar.setObjectName(u"statusbar")
|
||||||
@@ -94,6 +96,7 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Linux \u5b58\u50a8\u7ba1\u7406\u5de5\u5177", None))
|
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Linux \u5b58\u50a8\u7ba1\u7406\u5de5\u5177", None))
|
||||||
|
self.refreshButton.setText(QCoreApplication.translate("MainWindow", u"\u5237\u65b0\u6570\u636e", None))
|
||||||
___qtreewidgetitem = self.treeWidget_block_devices.headerItem()
|
___qtreewidgetitem = self.treeWidget_block_devices.headerItem()
|
||||||
___qtreewidgetitem.setText(12, QCoreApplication.translate("MainWindow", u"\u7236\u8bbe\u5907\u540d", None));
|
___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(11, QCoreApplication.translate("MainWindow", u"\u4e3b\u6b21\u53f7", None));
|
||||||
@@ -115,6 +118,5 @@ class Ui_MainWindow(object):
|
|||||||
___qtreewidgetitem2 = self.treeWidget_lvm.headerItem()
|
___qtreewidgetitem2 = self.treeWidget_lvm.headerItem()
|
||||||
___qtreewidgetitem2.setText(0, QCoreApplication.translate("MainWindow", u"1", None));
|
___qtreewidgetitem2.setText(0, QCoreApplication.translate("MainWindow", u"1", None));
|
||||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_lvm), QCoreApplication.translate("MainWindow", u"LVM \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
|
# retranslateUi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user