first commit

This commit is contained in:
zjing
2026-02-01 17:41:32 +08:00
commit d2fc416283
14 changed files with 1218 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.2, 2026-02-01T17:38:34. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{d278bd62-883b-4816-a712-738287b946d3}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">2</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Python 3.14.2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Python 3.14.2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{bf68bfb7-0daf-4abe-a8f9-98ada8c9b297}</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.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Python.PysideBuildStep</value>
<value type="QString" key="Python.PySideProjectTool">/home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/pyside6-project</value>
<value type="QString" key="Python.PySideUic">/home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/pyside6-uic</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">构建</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">构建</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">清除</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">清除</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<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="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<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">mainwindow.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/mainwindow.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/mainwindow.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.1">
<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">system_info.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/system_info.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/system_info.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.2">
<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">logger_config.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/logger_config.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/logger_config.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.3">
<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">disk_operations.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/disk_operations.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/disk_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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">4</value>
<value type="QString" key="python">/home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv/bin/python</value>
<value type="QString" key="venv">/home/smart/qtpj/diskmananger/.qtcreator/Python_3_14_2venv</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">部署</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">部署</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<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">mainwindow.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/mainwindow.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/mainwindow.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.1">
<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">system_info.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/system_info.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/system_info.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.2">
<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">logger_config.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/logger_config.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/logger_config.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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.3">
<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">disk_operations.py</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">PythonEditor.RunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/smart/qtpj/diskmananger/disk_operations.py</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">true</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="QString" key="PythonEditor.RunConfiguation.Script">/home/smart/qtpj/diskmananger/disk_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/smart/qtpj/diskmananger</value>
<value type="QString" key="RunConfiguration.X11Forwarding">:0.0</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">4</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

0
README.md Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

237
disk_operations.py Normal file
View File

@@ -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

141
form.ui Normal file
View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Linux 存储管理工具</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_block_devices">
<attribute name="title">
<string>块设备概览</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeWidget" name="treeWidget_block_devices">
<column>
<property name="text">
<string>设备名</string>
</property>
</column>
<column>
<property name="text">
<string>类型</string>
</property>
</column>
<column>
<property name="text">
<string>大小</string>
</property>
</column>
<column>
<property name="text">
<string>挂载点</string>
</property>
</column>
<column>
<property name="text">
<string>文件系统</string>
</property>
</column>
<column>
<property name="text">
<string>只读</string>
</property>
</column>
<column>
<property name="text">
<string>UUID</string>
</property>
</column>
<column>
<property name="text">
<string>PARTUUID</string>
</property>
</column>
<column>
<property name="text">
<string>厂商</string>
</property>
</column>
<column>
<property name="text">
<string>型号</string>
</property>
</column>
<column>
<property name="text">
<string>序列号</string>
</property>
</column>
<column>
<property name="text">
<string>主次号</string>
</property>
</column>
<column>
<property name="text">
<string>父设备名</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_raid">
<attribute name="title">
<string>RAID 管理</string>
</attribute>
</widget>
<widget class="QWidget" name="tab_lvm">
<attribute name="title">
<string>LVM 管理</string>
</attribute>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>刷新数据</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="logOutputTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

54
logger_config.py Normal file
View File

@@ -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__)

189
mainwindow.py Normal file
View File

@@ -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())

5
pyproject.toml Normal file
View File

@@ -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"]

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
PySide6

147
system_info.py Normal file
View File

@@ -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')})")

102
ui_form.py Normal file
View File

@@ -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