feat(core): 优化卸载功能并添加批量卸载支持

- 重构卸载流程,支持批量卸载补丁
- 新增已安装补丁游戏的检测和显示
- 改进用户界面,增加多选支持和更详细的结果反馈
- 优化代码结构,提高可维护性和可读性
This commit is contained in:
hyb-oyqq
2025-07-31 17:10:47 +08:00
parent c941c03446
commit c5b9f1746a
5 changed files with 386 additions and 198 deletions

View File

@@ -7,7 +7,7 @@ import re # Added for recursive search
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtGui import QIcon, QPixmap, QFont
from utils import msgbox_frame, HostsManager, resource_path
from data.config import APP_NAME, PLUGIN, GAME_INFO, UA, CONFIG_URL
@@ -264,8 +264,21 @@ class DownloadManager:
layout = QVBoxLayout(dialog)
# 添加说明标签
info_label = QLabel(f"请选择要安装补丁的游戏版本:\n{status_message}", dialog)
# 先显示已安装补丁的游戏
if already_installed_games:
already_installed_label = QLabel("已安装补丁的游戏:", dialog)
already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Bold))
layout.addWidget(already_installed_label)
already_installed_list = QLabel(chr(10).join(already_installed_games), dialog)
layout.addWidget(already_installed_list)
# 添加一些间距
layout.addSpacing(10)
# 添加"请选择你需要安装补丁的游戏"的标签
info_label = QLabel("请选择你需要安装补丁的游戏:", dialog)
info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Bold))
layout.addWidget(info_label)
# 添加列表控件
@@ -552,8 +565,9 @@ class DownloadManager:
timer.stop()
# 如果用户点击了取消安装
if result == QtWidgets.QMessageBox.StandardButton.RejectRole:
if msg_box.clickedButton() == cancel_button:
# 恢复主窗口状态
self.main_window.setEnabled(True)
self.main_window.ui.start_install_text.setText("开始安装")
# 清空下载队列
self.download_queue.clear()
@@ -604,7 +618,7 @@ class DownloadManager:
timer.stop()
# 如果用户点击了取消安装
if result == QtWidgets.QMessageBox.StandardButton.RejectRole:
if msg_box.clickedButton() == cancel_button:
# 恢复主窗口状态
self.main_window.setEnabled(True)
self.main_window.ui.start_install_text.setText("开始安装")
@@ -661,89 +675,9 @@ class DownloadManager:
print(f"DEBUG: 准备下载游戏 {game_version}")
print(f"DEBUG: 游戏文件夹: {game_folder}")
# 获取游戏可执行文件路径
game_dirs = self.main_window.game_detector.identify_game_directories_improved(self.selected_folder)
game_exe_exists = False
if game_version in game_dirs:
game_dir = game_dirs[game_version]
# 游戏目录已经通过可执行文件验证了,可以直接认为存在
# 游戏可执行文件已在填充下载队列时验证过,不需要再次检查
# 因为game_folder是从已验证的game_dirs中获取的
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 游戏目录已验证: {game_dir}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
else:
# 回退到传统方法检查游戏是否存在
# 尝试多种可能的文件名格式
expected_exe = GAME_INFO[game_version]["exe"]
traditional_folder = os.path.join(
self.selected_folder,
GAME_INFO[game_version]["install_path"].split("/")[0]
)
# 定义多种可能的可执行文件变体
exe_variants = [
expected_exe, # 标准文件名
expected_exe + ".nocrack", # Steam加密版本
expected_exe.replace(".exe", ""), # 无扩展名版本
expected_exe.replace("NEKOPARA", "nekopara").lower(), # 全小写变体
expected_exe.lower(), # 小写变体
expected_exe.lower() + ".nocrack", # 小写变体的Steam加密版本
]
# 对于Vol.3可能有特殊名称
if "Vol.3" in game_version:
# 增加可能的卷3特定的变体
exe_variants.extend([
"NEKOPARAVol3.exe",
"NEKOPARAVol3.exe.nocrack",
"nekoparavol3.exe",
"nekoparavol3.exe.nocrack",
"nekopara_vol3.exe",
"nekopara_vol3.exe.nocrack",
"vol3.exe",
"vol3.exe.nocrack"
])
# 检查所有可能的文件名
for exe_variant in exe_variants:
exe_path = os.path.join(traditional_folder, exe_variant)
if os.path.exists(exe_path):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 找到游戏可执行文件: {exe_path}")
break
# 如果仍未找到,尝试递归搜索
if not game_exe_exists and os.path.exists(traditional_folder):
# 提取卷号或检查是否是After
vol_match = re.search(r"Vol\.(\d+)", game_version)
vol_num = None
if vol_match:
vol_num = vol_match.group(1)
is_after = "After" in game_version
# 遍历游戏目录及其子目录
for root, dirs, files in os.walk(traditional_folder):
for file in files:
file_lower = file.lower()
if file.endswith('.exe') or file.endswith('.exe.nocrack'):
# 检查文件名中是否包含卷号或关键词
if ((vol_num and (f"vol{vol_num}" in file_lower or
f"vol.{vol_num}" in file_lower or
f"vol {vol_num}" in file_lower)) or
(is_after and "after" in file_lower)):
game_exe_exists = True
if debug_mode:
print(f"DEBUG: 通过递归搜索找到游戏可执行文件: {os.path.join(root, file)}")
break
if game_exe_exists:
break
if debug_mode:
print(f"DEBUG: 使用传统方法检查游戏目录: {traditional_folder}")
print(f"DEBUG: 游戏可执行文件存在: {game_exe_exists}")
# 检查游戏是否已安装
if (

View File

@@ -55,26 +55,29 @@ class PatchManager:
return self.installed_status.get(game_version, False)
return self.installed_status
def uninstall_patch(self, game_dir, game_version):
def uninstall_patch(self, game_dir, game_version, silent=False):
"""卸载补丁
Args:
game_dir: 游戏目录路径
game_version: 游戏版本
silent: 是否静默模式(不显示弹窗)
Returns:
bool: 卸载成功返回True失败返回False
dict: 在silent=True时返回包含卸载结果信息的字典
"""
debug_mode = self._is_debug_mode()
if game_version not in self.game_info:
if not silent:
QMessageBox.critical(
None,
f"错误 - {self.app_name}",
f"\n无法识别游戏版本: {game_version}\n",
QMessageBox.StandardButton.Ok,
)
return False
return False if not silent else {"success": False, "message": f"无法识别游戏版本: {game_version}", "files_removed": 0}
if debug_mode:
print(f"DEBUG: 开始卸载 {game_version} 补丁,目录: {game_dir}")
@@ -173,8 +176,8 @@ class PatchManager:
# 更新安装状态
self.installed_status[game_version] = False
# 在非批量卸载模式下显示卸载成功消息
if game_version != "all":
# 在非静默模式且非批量卸载模式下显示卸载成功消息
if not silent and game_version != "all":
# 显示卸载成功消息
if files_removed > 0:
QMessageBox.information(
@@ -192,11 +195,13 @@ class PatchManager:
)
# 卸载成功
if silent:
return {"success": True, "message": f"{game_version} 补丁卸载成功", "files_removed": files_removed}
return True
except Exception as e:
# 在非批量卸载模式下显示卸载失败消息
if game_version != "all":
# 在非静默模式且非批量卸载模式下显示卸载失败消息
if not silent and game_version != "all":
# 显示卸载失败消息
error_message = f"\n卸载 {game_version} 补丁时出错:\n\n{str(e)}\n"
if debug_mode:
@@ -210,6 +215,8 @@ class PatchManager:
)
# 卸载失败
if silent:
return {"success": False, "message": f"卸载 {game_version} 补丁时出错: {str(e)}", "files_removed": 0}
return False
def batch_uninstall_patches(self, game_dirs):
@@ -219,35 +226,166 @@ class PatchManager:
game_dirs: 游戏版本到游戏目录的映射字典
Returns:
tuple: (成功数量, 失败数量)
tuple: (成功数量, 失败数量, 详细结果列表)
"""
success_count = 0
fail_count = 0
debug_mode = self._is_debug_mode()
results = []
for version, path in game_dirs.items():
try:
if self.uninstall_patch(path, version):
# 在批量模式下使用静默卸载
result = self.uninstall_patch(path, version, silent=True)
if isinstance(result, dict): # 使用了静默模式
if result["success"]:
success_count += 1
else:
fail_count += 1
results.append({
"version": version,
"success": result["success"],
"message": result["message"],
"files_removed": result["files_removed"]
})
else: # 兼容旧代码,不应该执行到这里
if result:
success_count += 1
else:
fail_count += 1
results.append({
"version": version,
"success": result,
"message": f"{version} 卸载{'成功' if result else '失败'}",
"files_removed": 0
})
except Exception as e:
if debug_mode:
print(f"DEBUG: 卸载 {version} 时出错: {str(e)}")
fail_count += 1
results.append({
"version": version,
"success": False,
"message": f"卸载出错: {str(e)}",
"files_removed": 0
})
return success_count, fail_count
return success_count, fail_count, results
def show_uninstall_result(self, success_count, fail_count):
def show_uninstall_result(self, success_count, fail_count, results=None):
"""显示批量卸载结果
Args:
success_count: 成功卸载的数量
fail_count: 卸载失败的数量
results: 详细结果列表,如果提供,会显示更详细的信息
"""
result_text = f"\n批量卸载完成!\n成功: {success_count}\n失败: {fail_count}\n"
# 如果有详细结果,添加到消息中
if results:
success_list = [r["version"] for r in results if r["success"]]
fail_list = [r["version"] for r in results if not r["success"]]
if success_list:
result_text += f"\n【成功卸载】:\n{chr(10).join(success_list)}\n"
if fail_list:
result_text += f"\n【卸载失败】:\n{chr(10).join(fail_list)}\n"
QMessageBox.information(
None,
f"批量卸载完成 - {self.app_name}",
f"\n批量卸载完成!\n成功: {success_count}\n失败: {fail_count}\n",
result_text,
QMessageBox.StandardButton.Ok,
)
def check_patch_installed(self, game_dir, game_version):
"""检查游戏是否已安装补丁
Args:
game_dir: 游戏目录路径
game_version: 游戏版本
Returns:
bool: 如果已安装补丁返回True否则返回False
"""
debug_mode = self._is_debug_mode()
if game_version not in self.game_info:
return False
# 获取可能的补丁文件路径
install_path_base = os.path.basename(self.game_info[game_version]["install_path"])
patch_file_path = os.path.join(game_dir, install_path_base)
# 尝试查找补丁文件,支持不同大小写
patch_files_to_check = [
patch_file_path,
patch_file_path.lower(),
patch_file_path.upper(),
patch_file_path.replace("_", ""),
patch_file_path.replace("_", "-"),
]
# 查找补丁文件
for patch_path in patch_files_to_check:
if os.path.exists(patch_path):
if debug_mode:
print(f"DEBUG: 找到补丁文件: {patch_path}")
return True
# 检查是否有补丁文件夹
patch_folders_to_check = [
os.path.join(game_dir, "patch"),
os.path.join(game_dir, "Patch"),
os.path.join(game_dir, "PATCH"),
]
for patch_folder in patch_folders_to_check:
if os.path.exists(patch_folder):
if debug_mode:
print(f"DEBUG: 找到补丁文件夹: {patch_folder}")
return True
# 检查game/patch文件夹
game_folders = ["game", "Game", "GAME"]
patch_folders = ["patch", "Patch", "PATCH"]
for game_folder in game_folders:
for patch_folder in patch_folders:
game_patch_folder = os.path.join(game_dir, game_folder, patch_folder)
if os.path.exists(game_patch_folder):
if debug_mode:
print(f"DEBUG: 找到game/patch文件夹: {game_patch_folder}")
return True
# 检查配置文件
config_files = ["config.json", "Config.json", "CONFIG.JSON"]
script_files = ["scripts.json", "Scripts.json", "SCRIPTS.JSON"]
for game_folder in game_folders:
game_path = os.path.join(game_dir, game_folder)
if os.path.exists(game_path):
# 检查配置文件
for config_file in config_files:
config_path = os.path.join(game_path, config_file)
if os.path.exists(config_path):
if debug_mode:
print(f"DEBUG: 找到配置文件: {config_path}")
return True
# 检查脚本文件
for script_file in script_files:
script_path = os.path.join(game_path, script_file)
if os.path.exists(script_path):
if debug_mode:
print(f"DEBUG: 找到脚本文件: {script_path}")
return True
# 没有找到补丁文件或文件夹
if debug_mode:
print(f"DEBUG: {game_version}{game_dir} 中没有安装补丁")
return False

View File

@@ -3,7 +3,7 @@ import base64
# 配置信息
app_data = {
"APP_VERSION": "1.2.0",
"APP_VERSION": "1.3.0",
"APP_NAME": "FRAISEMOE Addons Installer NEXT",
"TEMP": "TEMP",
"CACHE": "FRAISEMOE",

View File

@@ -7,7 +7,7 @@ import webbrowser
from PySide6 import QtWidgets
from PySide6.QtCore import QTimer, Qt, QPoint, QRect, QSize
from PySide6.QtWidgets import QMainWindow, QMessageBox, QGraphicsOpacityEffect, QGraphicsColorizeEffect
from PySide6.QtGui import QPalette, QColor, QPainterPath, QRegion
from PySide6.QtGui import QPalette, QColor, QPainterPath, QRegion, QFont
from PySide6.QtGui import QAction # Added for menu actions
from ui.Ui_install import Ui_MainWindows
@@ -372,26 +372,23 @@ class MainWindow(QMainWindow):
# 构建结果信息
result_text = f"\n安装结果:\n"
# 总数统计
# 总数统计 - 不再显示已跳过的数量
total_installed = len(installed_versions)
total_skipped = len(skipped_versions)
total_failed = len(failed_versions)
total_not_found = len(not_found_versions)
result_text += f"安装成功:{total_installed} 已跳过:{total_skipped} 安装失败:{total_failed}\n\n"
result_text += f"安装成功:{total_installed} 个 安装失败:{total_failed}\n\n"
# 详细列表
if installed_versions:
result_text += f"【成功安装】:\n{chr(10).join(installed_versions)}\n\n"
if skipped_versions:
result_text += f"【已安装跳过】:\n{chr(10).join(skipped_versions)}\n\n"
if failed_versions:
result_text += f"【安装失败】:\n{chr(10).join(failed_versions)}\n\n"
if not_found_versions and (installed_versions or failed_versions):
# 只有当有其他版本存在时,才显示未找到的版本
result_text += f"未在指定目录找到】:\n{chr(10).join(not_found_versions)}\n"
if not_found_versions:
# 将"未在指定目录找到"改为"尚未安装补丁的游戏"
result_text += f"尚未安装补丁的游戏】:\n{chr(10).join(not_found_versions)}\n"
QMessageBox.information(
self,
@@ -528,47 +525,110 @@ class MainWindow(QMainWindow):
game_dirs = self.game_detector.identify_game_directories_improved(selected_folder)
if game_dirs and len(game_dirs) > 0:
# 找到了游戏目录,显示选择对话框
if debug_mode:
print(f"DEBUG: 卸载功能 - 在上级目录中找到以下游戏: {list(game_dirs.keys())}")
# 如果只有一个游戏,直接选择它
if len(game_dirs) == 1:
game_version = list(game_dirs.keys())[0]
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
else:
# 有多个游戏,让用户选择
from PySide6.QtWidgets import QInputDialog
game_versions = list(game_dirs.keys())
# 添加"全部卸载"选项
game_versions.append("全部卸载")
# 查找已安装补丁的游戏,只处理那些已安装补丁的游戏
games_with_patch = {}
for game_version, game_dir in game_dirs.items():
if self.patch_manager.check_patch_installed(game_dir, game_version):
games_with_patch[game_version] = game_dir
if debug_mode:
print(f"DEBUG: 卸载功能 - {game_version} 已安装补丁")
selected_game, ok = QInputDialog.getItem(
self, "选择游戏", "选择要卸载补丁的游戏:",
game_versions, 0, False
# 检查是否有已安装补丁的游戏
if not games_with_patch:
QMessageBox.information(
self,
f"提示 - {APP_NAME}",
"\n未在选择的目录中找到已安装补丁的游戏。\n请确认您选择了正确的游戏目录,并且该目录中的游戏已安装过补丁。\n",
QMessageBox.StandardButton.Ok
)
return
if ok and selected_game:
if selected_game == "全部卸载":
# 卸载所有游戏补丁
# 创建自定义选择对话框,允许多选
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QListWidget, QPushButton, QHBoxLayout, QAbstractItemView
dialog = QDialog(self)
dialog.setWindowTitle("选择要卸载的游戏补丁")
dialog.resize(400, 300)
layout = QVBoxLayout(dialog)
# 添加"已安装补丁的游戏"标签
already_installed_label = QLabel("已安装补丁的游戏:", dialog)
already_installed_label.setFont(QFont(already_installed_label.font().family(), already_installed_label.font().pointSize(), QFont.Bold))
layout.addWidget(already_installed_label)
# 添加已安装游戏列表(可选,这里使用静态标签替代,保持一致性)
installed_games_text = ", ".join(games_with_patch.keys())
installed_games_label = QLabel(installed_games_text, dialog)
layout.addWidget(installed_games_label)
# 添加一些间距
layout.addSpacing(10)
# 添加"请选择要卸载补丁的游戏"标签
info_label = QLabel("请选择要卸载补丁的游戏:", dialog)
info_label.setFont(QFont(info_label.font().family(), info_label.font().pointSize(), QFont.Bold))
layout.addWidget(info_label)
# 添加列表控件,只显示已安装补丁的游戏
list_widget = QListWidget(dialog)
list_widget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) # 允许多选
for game in games_with_patch.keys():
list_widget.addItem(game)
layout.addWidget(list_widget)
# 添加全选按钮
select_all_btn = QPushButton("全选", dialog)
select_all_btn.clicked.connect(lambda: list_widget.selectAll())
layout.addWidget(select_all_btn)
# 添加确定和取消按钮
buttons_layout = QHBoxLayout()
ok_button = QPushButton("确定", dialog)
cancel_button = QPushButton("取消", dialog)
buttons_layout.addWidget(ok_button)
buttons_layout.addWidget(cancel_button)
layout.addLayout(buttons_layout)
# 连接按钮事件
ok_button.clicked.connect(dialog.accept)
cancel_button.clicked.connect(dialog.reject)
# 显示对话框并等待用户选择
result = dialog.exec()
if result != QDialog.DialogCode.Accepted or list_widget.selectedItems() == []:
# 用户取消或未选择任何游戏
return
# 获取用户选择的游戏
selected_games = [item.text() for item in list_widget.selectedItems()]
if debug_mode:
print(f"DEBUG: 卸载功能 - 用户选择了以下游戏进行卸载: {selected_games}")
# 过滤game_dirs只保留选中的游戏
selected_game_dirs = {game: games_with_patch[game] for game in selected_games if game in games_with_patch}
# 确认卸载
game_list = '\n'.join(selected_games)
reply = QMessageBox.question(
self,
f"确认卸载 - {APP_NAME}",
f"\n确定要卸载所有游戏的补丁吗?\n这将卸载以下游戏的补丁:\n{chr(10).join(list(game_dirs.keys()))}\n",
f"\n确定要卸载以下游戏的补丁吗?\n\n{game_list}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
if reply == QMessageBox.StandardButton.No:
return
# 使用批量卸载方法
success_count, fail_count = self.patch_manager.batch_uninstall_patches(game_dirs)
self.patch_manager.show_uninstall_result(success_count, fail_count)
else:
# 卸载选中的单个游戏
game_version = selected_game
game_dir = game_dirs[game_version]
self._confirm_and_uninstall(game_dir, game_version)
success_count, fail_count, results = self.patch_manager.batch_uninstall_patches(selected_game_dirs)
self.patch_manager.show_uninstall_result(success_count, fail_count, results)
else:
# 未找到游戏目录,尝试将选择的目录作为游戏目录
if debug_mode:
@@ -579,7 +639,31 @@ class MainWindow(QMainWindow):
if game_version:
if debug_mode:
print(f"DEBUG: 卸载功能 - 识别为游戏: {game_version}")
self._confirm_and_uninstall(selected_folder, game_version)
# 检查是否已安装补丁
if self.patch_manager.check_patch_installed(selected_folder, game_version):
# 确认卸载
reply = QMessageBox.question(
self,
f"确认卸载 - {APP_NAME}",
f"\n确定要卸载 {game_version} 的补丁吗?\n游戏目录: {selected_folder}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
# 创建单个游戏的目录字典,使用批量卸载流程
single_game_dir = {game_version: selected_folder}
success_count, fail_count, results = self.patch_manager.batch_uninstall_patches(single_game_dir)
self.patch_manager.show_uninstall_result(success_count, fail_count, results)
else:
# 没有安装补丁
QMessageBox.information(
self,
f"提示 - {APP_NAME}",
f"\n未在 {game_version} 中找到已安装的补丁。\n请确认该游戏已经安装过补丁。\n",
QMessageBox.StandardButton.Ok
)
else:
# 两种方式都未识别到游戏
if debug_mode:
@@ -592,28 +676,6 @@ class MainWindow(QMainWindow):
)
msg_box.exec()
def _confirm_and_uninstall(self, game_dir, game_version):
"""确认并卸载补丁
Args:
game_dir: 游戏目录
game_version: 游戏版本
"""
debug_mode = self.debug_manager._is_debug_mode()
# 确认卸载
reply = QMessageBox.question(
self,
f"确认卸载 - {APP_NAME}",
f"\n确定要卸载 {game_version} 的补丁吗?\n游戏目录: {game_dir}\n",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.No:
return
# 开始卸载补丁
self.patch_manager.uninstall_patch(game_dir, game_version)

View File

@@ -9,6 +9,25 @@ from PySide6.QtWidgets import (QLabel, QProgressBar, QVBoxLayout, QDialog, QHBox
from utils import resource_path
from data.config import APP_NAME, UA
import signal
import ctypes
# Windows API常量和函数
if sys.platform == 'win32':
kernel32 = ctypes.windll.kernel32
PROCESS_ALL_ACCESS = 0x1F0FFF
THREAD_SUSPEND_RESUME = 0x0002
TH32CS_SNAPTHREAD = 0x00000004
class THREADENTRY32(ctypes.Structure):
_fields_ = [
('dwSize', ctypes.c_ulong),
('cntUsage', ctypes.c_ulong),
('th32ThreadID', ctypes.c_ulong),
('th32OwnerProcessID', ctypes.c_ulong),
('tpBasePri', ctypes.c_ulong),
('tpDeltaPri', ctypes.c_ulong),
('dwFlags', ctypes.c_ulong)
]
# 下载线程类
class DownloadThread(QThread):
@@ -23,7 +42,7 @@ class DownloadThread(QThread):
self.process = None
self._is_running = True
self._is_paused = False
self.pause_process = None
self.threads = []
def stop(self):
if self.process and self.process.poll() is None:
@@ -34,24 +53,56 @@ class DownloadThread(QThread):
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"停止下载进程时出错: {e}")
def _get_process_threads(self, pid):
"""获取进程的所有线程ID"""
if sys.platform != 'win32':
return []
thread_ids = []
h_snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)
if h_snapshot == -1:
return []
thread_entry = THREADENTRY32()
thread_entry.dwSize = ctypes.sizeof(THREADENTRY32)
res = kernel32.Thread32First(h_snapshot, ctypes.byref(thread_entry))
while res:
if thread_entry.th32OwnerProcessID == pid:
thread_ids.append(thread_entry.th32ThreadID)
res = kernel32.Thread32Next(h_snapshot, ctypes.byref(thread_entry))
kernel32.CloseHandle(h_snapshot)
return thread_ids
def pause(self):
"""暂停下载进程"""
if not self._is_paused and self.process and self.process.poll() is None:
try:
# 使用SIGSTOP信号暂停进程
# Windows下使用不同的方式因为没有SIGSTOP
if sys.platform == 'win32':
# 获取所有线程
self.threads = self._get_process_threads(self.process.pid)
if not self.threads:
print("未找到可暂停的线程")
return False
# 暂停所有线程
for thread_id in self.threads:
h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id)
if h_thread:
kernel32.SuspendThread(h_thread)
kernel32.CloseHandle(h_thread)
self._is_paused = True
# 在Windows上使用暂停进程的方法
self.pause_process = subprocess.Popen(['powershell', '-Command', f'(Get-Process -Id {self.process.pid}).Suspend()'],
creationflags=subprocess.CREATE_NO_WINDOW)
print(f"下载进程已暂停: PID {self.process.pid}, 线程数: {len(self.threads)}")
return True
else:
# 在Unix系统上使用SIGSTOP
os.kill(self.process.pid, signal.SIGSTOP)
self._is_paused = True
print(f"下载进程已暂停: PID {self.process.pid}")
return True
except (subprocess.CalledProcessError, FileNotFoundError, OSError) as e:
except Exception as e:
print(f"暂停下载进程时出错: {e}")
return False
return False
@@ -60,21 +111,24 @@ class DownloadThread(QThread):
"""恢复下载进程"""
if self._is_paused and self.process and self.process.poll() is None:
try:
# 使用SIGCONT信号恢复进程
# Windows下使用不同的方式
if sys.platform == 'win32':
# 恢复所有线程
for thread_id in self.threads:
h_thread = kernel32.OpenThread(THREAD_SUSPEND_RESUME, False, thread_id)
if h_thread:
kernel32.ResumeThread(h_thread)
kernel32.CloseHandle(h_thread)
self._is_paused = False
# 在Windows上使用恢复进程的方法
resume_process = subprocess.Popen(['powershell', '-Command', f'(Get-Process -Id {self.process.pid}).Resume()'],
creationflags=subprocess.CREATE_NO_WINDOW)
resume_process.wait()
print(f"下载进程已恢复: PID {self.process.pid}, 线程数: {len(self.threads)}")
return True
else:
# 在Unix系统上使用SIGCONT
os.kill(self.process.pid, signal.SIGCONT)
self._is_paused = False
print(f"下载进程已恢复: PID {self.process.pid}")
return True
except (subprocess.CalledProcessError, FileNotFoundError, OSError) as e:
except Exception as e:
print(f"恢复下载进程时出错: {e}")
return False
return False