diff --git a/build.sh b/build.sh index ef791df..e76085e 100644 --- a/build.sh +++ b/build.sh @@ -1,164 +1,113 @@ #!/bin/bash # -# Linux 存储管理器 - PyInstaller 打包脚本 -# 自动安装依赖并打包应用程序 +# Linux 存储管理器 - PyInstaller 打包脚本 (简化版) +# 直接打包,仅保留 dist 目录中的最终文件 # set -e -echo "" -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ Linux 存储管理器 - PyInstaller 打包工具 ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" - -# 检查 Python3 -if ! command -v python3 &> /dev/null; then - echo "✗ 错误: 未找到 python3" - echo " 请先安装 Python 3.6+" - exit 1 -fi - -PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') -echo " Python 版本: $PYTHON_VERSION" - -# 检查主程序文件 -if [ ! -f "mainwindow_tkinter.py" ] && [ ! -f "mainwindow.py" ]; then - echo "✗ 错误: 未找到主程序文件" - echo " 请确保在包含 mainwindow_tkinter.py 或 mainwindow.py 的目录中运行此脚本" - exit 1 -fi - -echo "" -echo "═══════════════════════════════════════════════════════════════" -echo " 步骤 1/4: 安装依赖包" -echo "═══════════════════════════════════════════════════════════════" - -# 检测是否为外部管理环境 (PEP 668) -IS_EXTERNALLY_MANAGED=0 -if python3 -c "import sys; sys.exit(0 if (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) or 'VIRTUAL_ENV' in __import__('os').environ else 1)" 2>/dev/null; then - echo " 检测到在虚拟环境中" - IS_EXTERNALLY_MANAGED=0 -elif python3 -c "import sysconfig; import os; print(os.path.exists(os.path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')))" | grep -q "True"; then - echo " 检测到外部管理环境 (PEP 668)" - IS_EXTERNALLY_MANAGED=1 -fi - -# 安装依赖 -install_deps() { - local EXTRA_ARGS="" - if [ "$IS_EXTERNALLY_MANAGED" -eq 1 ]; then - EXTRA_ARGS="--break-system-packages" - echo " 使用 --break-system-packages 选项" - fi - - # 安装/升级 pip - python3 -m pip install --upgrade pip $EXTRA_ARGS 2>/dev/null || { - echo " pip 升级失败,继续安装..." - } - - # 安装 pyinstaller 和 pexpect - pip3 install pyinstaller pexpect $EXTRA_ARGS || { - return 1 - } - return 0 -} - -if ! install_deps; then - echo " 尝试使用 sudo 安装依赖..." - sudo pip3 install pyinstaller pexpect --break-system-packages 2>/dev/null || { - echo "" - echo "✗ 依赖安装失败" - echo "" - echo " 您可以选择以下方式安装:" - echo "" - echo " 方式 1 - 创建虚拟环境 (推荐):" - echo " python3 -m venv venv" - echo " source venv/bin/activate" - echo " pip install pyinstaller pexpect" - echo " ./build.sh" - echo "" - echo " 方式 2 - 使用系统包管理器 (Arch):" - echo " sudo pacman -S python-pyinstaller python-pexpect" - echo " ./build.sh" - echo "" - echo " 方式 3 - 强制安装 (有风险):" - echo " pip3 install pyinstaller pexpect --break-system-packages" - echo " ./build.sh" - echo "" - exit 1 - } -fi - -echo "" -echo "═══════════════════════════════════════════════════════════════" -echo " 步骤 2/4: 清理构建文件" -echo "═══════════════════════════════════════════════════════════════" - -# 清理旧文件 -rm -rf build dist __pycache__ -rm -f mainwindow*.spec - -echo " 已清理构建文件" - -echo "" -echo "═══════════════════════════════════════════════════════════════" -echo " 步骤 3/4: PyInstaller 打包" -echo "═══════════════════════════════════════════════════════════════" - # 确定主文件 MAIN_SCRIPT="mainwindow_tkinter.py" if [ ! -f "$MAIN_SCRIPT" ]; then MAIN_SCRIPT="mainwindow.py" fi -echo " 打包文件: $MAIN_SCRIPT" +# 检查 tkinter +if ! python3 -c "import tkinter" 2>/dev/null; then + echo "错误: 未找到 tkinter 模块" + exit 1 +fi + +# 获取 tkinter 路径 +TKINTER_INFO=$(python3 << 'EOF' +import tkinter +import os +import sys + +tcl = tkinter.Tcl() +tk_version = tcl.call("info", "tclversion") +tk_lib = tcl.call("info", "library") +print(f"TK_VERSION:{tk_version}") +print(f"TK_LIBRARY:{tk_lib}") + +tkinter_file = tkinter.__file__ +print(f"TKINTER_FILE:{tkinter_file}") + +tcl_lib = os.path.dirname(tk_lib) +print(f"TCL_LIBRARY:{tcl_lib}") + +import sysconfig +lib_dynload = os.path.join(sysconfig.get_config_var('LIBDIR'), f"python{sys.version_info.major}.{sys.version_info.minor}", "lib-dynload") +if not os.path.exists(lib_dynload): + lib_dynload = os.path.join(sysconfig.get_config_var('LIBDIR'), "lib-dynload") +print(f"LIB_DYNLOAD:{lib_dynload}") +EOF +) + +TK_VERSION=$(echo "$TKINTER_INFO" | grep "TK_VERSION:" | cut -d: -f2) +TK_LIBRARY=$(echo "$TKINTER_INFO" | grep "TK_LIBRARY:" | cut -d: -f2) +TCL_LIBRARY=$(echo "$TKINTER_INFO" | grep "TCL_LIBRARY:" | cut -d: -f2) +LIB_DYNLOAD=$(echo "$TKINTER_INFO" | grep "LIB_DYNLOAD:" | cut -d: -f2) +PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') + +# 查找 _tkinter 共享库 +TKINTER_SO="" +for pattern in "_tkinter.cpython-${PYTHON_VERSION//./}m-x86_64-linux-gnu.so" "_tkinter.cpython-${PYTHON_VERSION//./}-x86_64-linux-gnu.so" "_tkinter.so"; do + if [ -f "$LIB_DYNLOAD/$pattern" ]; then + TKINTER_SO="$LIB_DYNLOAD/$pattern" + break + fi +done +if [ -z "$TKINTER_SO" ]; then + TKINTER_SO=$(find $LIB_DYNLOAD -name "_tkinter*.so" 2>/dev/null | head -1) +fi + +# 构建 PyInstaller 参数 +PYINSTALLER_ARGS=( + --onefile + --name "linux-storage-manager" + --clean + --noconfirm + --console + --hidden-import "tkinter" + --hidden-import "tkinter.ttk" + --hidden-import "tkinter.scrolledtext" + --hidden-import "tkinter.messagebox" + --hidden-import "tkinter.filedialog" + --hidden-import "tkinter.simpledialog" + --hidden-import "_tkinter" +) + +# 添加 tkinter 库文件 +if [ -n "$TKINTER_SO" ]; then + PYINSTALLER_ARGS+=(--add-binary "$TKINTER_SO:.") +fi +if [ -d "$TCL_LIBRARY" ]; then + PYINSTALLER_ARGS+=(--add-data "$TCL_LIBRARY:tcl") +fi +if [ -d "$TK_LIBRARY" ]; then + PYINSTALLER_ARGS+=(--add-data "$TK_LIBRARY:tk") +fi + +# 查找并添加其他 Tcl/Tk 文件 +for dir in /usr/lib64/tcl$TK_VERSION /usr/lib/tcl$TK_VERSION /usr/share/tcltk /usr/lib/tcltk /usr/lib64/tcltk; do + if [ -d "$dir" ]; then + PYINSTALLER_ARGS+=(--add-data "$dir:tcltk") + fi +done # 执行打包 -python3 -m PyInstaller \ - --onefile \ - --name "linux-storage-manager" \ - --clean \ - --noconfirm \ - --console \ - "$MAIN_SCRIPT" +python3 -m PyInstaller "${PYINSTALLER_ARGS[@]}" "$MAIN_SCRIPT" -echo "" -echo "═══════════════════════════════════════════════════════════════" -echo " 步骤 4/4: 创建桌面快捷方式" -echo "═══════════════════════════════════════════════════════════════" - -cat > linux-storage-manager.desktop << 'EOF' -[Desktop Entry] -Name=Linux 存储管理器 -Comment=Linux 存储管理工具 -Exec=/usr/local/bin/linux-storage-manager -Icon=drive-harddisk -Terminal=false -Type=Application -Categories=System;Utility; -Keywords=storage;disk;partition;raid;lvm; -EOF - -echo " 已创建桌面文件: linux-storage-manager.desktop" -echo " 安装命令: sudo cp linux-storage-manager.desktop /usr/share/applications/" - -# 检查输出 +# 检查结果 if [ -f "dist/linux-storage-manager" ]; then SIZE=$(du -h dist/linux-storage-manager | cut -f1) echo "" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ 打包成功! ║" - echo "╚══════════════════════════════════════════════════════════════╝" - echo "" + echo "打包成功!" echo " 输出文件: dist/linux-storage-manager" echo " 文件大小: $SIZE" - echo "" - echo " 使用方法:" - echo " 直接运行: sudo dist/linux-storage-manager" - echo " 安装到系统: sudo cp dist/linux-storage-manager /usr/local/bin/" - echo "" + echo " 运行命令: sudo dist/linux-storage-manager" else - echo "✗ 打包失败: 未找到输出文件" + echo "打包失败" exit 1 fi diff --git a/disk_operations_tkinter.py b/disk_operations_tkinter.py index 5d9d941..1ebc9fc 100644 --- a/disk_operations_tkinter.py +++ b/disk_operations_tkinter.py @@ -190,26 +190,48 @@ class DiskOperations: logger.info(f"尝试挂载设备 {device_path} 到 {mount_point}。") success, _, stderr = self._execute_shell_command( ["mount", device_path, mount_point], - f"挂载设备 {device_path} 失败" + f"挂载设备 {device_path} 失败", + show_dialog=False ) - if success: - messagebox.showinfo("成功", f"设备 {device_path} 已成功挂载到 {mount_point}。") - if add_to_fstab: - device_details = self.system_manager.get_device_details_by_path(device_path) - if device_details: - fstype = device_details.get('fstype') - uuid = device_details.get('uuid') - if fstype and uuid: - self._add_to_fstab(device_path, mount_point, fstype, uuid) - else: - logger.error(f"无法获取设备 {device_path} 的文件系统类型或 UUID 以添加到 fstab。") - messagebox.showwarning("警告", f"设备 {device_path} 已挂载,但无法获取文件系统类型或 UUID 以添加到 fstab。") + + # 如果普通挂载失败,检查是否是 NTFS 并尝试使用 ntfs-3g + if not success: + device_details = self.system_manager.get_device_details_by_path(device_path) + fstype = device_details.get('fstype', '').lower() if device_details else '' + + if fstype == 'ntfs' or 'ntfs' in stderr.lower(): + logger.info(f"尝试使用 ntfs-3g 挂载设备 {device_path}。") + success, _, stderr = self._execute_shell_command( + ["mount", "-t", "ntfs-3g", device_path, mount_point], + f"使用 ntfs-3g 挂载设备 {device_path} 失败" + ) + if success: + messagebox.showinfo("成功", f"设备 {device_path} 已通过 ntfs-3g 成功挂载到 {mount_point}。") else: - logger.error(f"无法获取设备 {device_path} 的详细信息以添加到 fstab。") - messagebox.showwarning("警告", f"设备 {device_path} 已挂载,但无法获取详细信息以添加到 fstab。") - return True + messagebox.showerror("错误", f"挂载设备 {device_path} 失败。\n\n" + f"普通挂载和 ntfs-3g 挂载均失败。\n" + f"如果是 NTFS 文件系统损坏,请先使用文件系统工具中的 "修复文件系统 (ntfsfix)" 修复。") + return False + else: + messagebox.showerror("错误", f"挂载设备 {device_path} 失败: {stderr}") + return False else: - return False + messagebox.showinfo("成功", f"设备 {device_path} 已成功挂载到 {mount_point}。") + + if success and add_to_fstab: + device_details = self.system_manager.get_device_details_by_path(device_path) + if device_details: + fstype = device_details.get('fstype') + uuid = device_details.get('uuid') + if fstype and uuid: + self._add_to_fstab(device_path, mount_point, fstype, uuid) + else: + logger.error(f"无法获取设备 {device_path} 的文件系统类型或 UUID 以添加到 fstab。") + messagebox.showwarning("警告", f"设备 {device_path} 已挂载,但无法获取文件系统类型或 UUID 以添加到 fstab。") + else: + logger.error(f"无法获取设备 {device_path} 的详细信息以添加到 fstab。") + messagebox.showwarning("警告", f"设备 {device_path} 已挂载,但无法获取详细信息以添加到 fstab。") + return success def unmount_partition(self, device_path, show_dialog_on_error=True): """卸载指定设备""" diff --git a/diskmanager.spec b/diskmanager.spec new file mode 100644 index 0000000..28240f6 --- /dev/null +++ b/diskmanager.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['main_tkinter.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='diskmanager', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/linux-storage-manager.spec b/linux-storage-manager.spec index ffa091d..b840a88 100644 --- a/linux-storage-manager.spec +++ b/linux-storage-manager.spec @@ -4,9 +4,9 @@ a = Analysis( ['mainwindow_tkinter.py'], pathex=[], - binaries=[], - datas=[], - hiddenimports=[], + binaries=[('/usr/lib/python3.14/lib-dynload/_tkinter.cpython-314-x86_64-linux-gnu.so', '.')], + datas=[('/usr/lib', 'tcl'), ('/usr/lib/tcl8.6', 'tk'), ('/usr/lib64/tcl8.6', 'tcltk'), ('/usr/lib/tcl8.6', 'tcltk')], + hiddenimports=['tkinter', 'tkinter.ttk', 'tkinter.scrolledtext', 'tkinter.messagebox', 'tkinter.filedialog', 'tkinter.simpledialog', '_tkinter'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/mainwindow_tkinter.py b/mainwindow_tkinter.py index 59cd679..397df98 100644 --- a/mainwindow_tkinter.py +++ b/mainwindow_tkinter.py @@ -440,6 +440,9 @@ class MainWindow: elif fstype == 'xfs': fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {device_path}", command=lambda dp=device_path: self._handle_xfs_repair(dp)) + elif fstype.lower() == 'ntfs': + fs_menu.add_command(label=f"修复文件系统 (ntfsfix) {device_path}", + command=lambda dp=device_path: self._handle_ntfsfix(dp)) if fs_menu.index(tk.END) is not None: menu.add_cascade(label=f"文件系统工具 {device_path}", menu=fs_menu) @@ -546,6 +549,9 @@ class MainWindow: elif fstype == 'xfs': fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {array_path}", command=lambda dp=array_path: self._handle_xfs_repair(dp)) + elif fstype.lower() == 'ntfs': + fs_menu.add_command(label=f"修复文件系统 (ntfsfix) {array_path}", + command=lambda dp=array_path: self._handle_ntfsfix(dp)) if fs_menu.index(tk.END) is not None: menu.add_cascade(label=f"文件系统工具 {array_path}", menu=fs_menu) @@ -637,6 +643,9 @@ class MainWindow: elif fstype == 'xfs': fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {lv_path}", command=lambda dp=lv_path: self._handle_xfs_repair(dp)) + elif fstype.lower() == 'ntfs': + fs_menu.add_command(label=f"修复文件系统 (ntfsfix) {lv_path}", + command=lambda dp=lv_path: self._handle_ntfsfix(dp)) if fs_menu.index(tk.END) is not None: menu.add_cascade(label=f"文件系统工具 {lv_path}", menu=fs_menu) @@ -918,6 +927,37 @@ class MainWindow: else: messagebox.showerror("错误", f"调整文件系统大小失败:\n{stderr[:500] if stderr else '未知错误'}") self.refresh_all_info() + + def _handle_ntfsfix(self, device_path): + """修复 NTFS 文件系统""" + # 检查设备是否已挂载 + mount_point = self.system_manager.get_mountpoint_for_device(device_path) + if mount_point and mount_point != 'N/A': + messagebox.showwarning("警告", f"设备 {device_path} 已挂载到 {mount_point}\n请先卸载后再执行文件系统修复。") + return + + if not messagebox.askyesno("确认", + f"即将对 {device_path} (NTFS) 执行文件系统修复。\n" + f"命令: sudo ntfsfix -d {device_path}\n\n" + f"注意:此操作会清除 NTFS 卷的脏标志并尝试修复。\n" + f"如果修复后仍无法挂载,请尝试使用 ntfs-3g 方式挂载。", + default=messagebox.NO): + return + + logger.info(f"开始修复 NTFS 文件系统: {device_path}") + success, stdout, stderr = self.lvm_ops._execute_shell_command( + ["ntfsfix", "-d", device_path], + f"修复 NTFS 文件系统 {device_path} 失败" + ) + + if success: + messagebox.showinfo("成功", f"NTFS 文件系统修复完成:\n{stdout[:500] if stdout else '无输出'}\n\n" + f"如果仍无法正常挂载,请尝试使用:\n" + f"mount -t ntfs-3g {device_path} <挂载点>") + else: + messagebox.showerror("错误", f"NTFS 文件系统修复失败:\n{stderr[:500] if stderr else '未知错误'}\n\n" + f"建议尝试使用 ntfs-3g 驱动挂载。") + self.refresh_all_info() def on_disk_formatting_finished(self, success, device_path, stdout, stderr): """接收格式化完成回调并刷新界面"""