From 58a750cdb8f65bd73eec1dd413eee82b5b2bddf7 Mon Sep 17 00:00:00 2001 From: 27942 Date: Mon, 26 Jan 2026 17:40:45 +0800 Subject: [PATCH] hahaa --- gui_app.py | 255 +++++++++++++++++++---------------------------------- main.py | 26 +++++- 2 files changed, 116 insertions(+), 165 deletions(-) diff --git a/gui_app.py b/gui_app.py index a14804c..b692218 100644 --- a/gui_app.py +++ b/gui_app.py @@ -104,84 +104,44 @@ class WorkerThread(QThread): self.log_message.emit(f"使用预查找的文件列表,共 {len(self.prepared_files)} 个文件") self.progress.emit(30) - # 判断是否为视频文件 - video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm'] - video_files = [f for f in self.prepared_files if - f['path'].is_file() and any(f['path'].suffix.lower() == ext for ext in video_extensions)] - - # 如果勾选了批量上传且是视频,调用action1方法 - if self.is_batch_mode and video_files: - # 批量上传视频,调用action1方法 - self.log_message.emit(f"批量上传模式:找到 {len(video_files)} 个视频文件,调用action1方法") - try: - result = pdd.action1(folder_path=video_files) - ok = bool(result.get("ok")) if isinstance(result, dict) else True - if not ok: - fails = [r for r in (result.get("results") or []) if not r.get("ok")] - self.log_message.emit(f"发布校验失败条数: {len(fails)}") - for r in fails[:20]: - self.log_message.emit(f" ✗ {r.get('name')} | {r.get('reason')}") - # 逐条回传结果给GUI更新表格状态 - if isinstance(result, dict): - for r in (result.get("results") or []): - try: - payload = dict(r) - payload["user_id"] = config.get("多多id", "") - self.item_result.emit(payload) - except Exception: - pass - self.progress.emit(100) - self.finished.emit(ok, f"批量上传完成,共处理 {len(video_files)} 个视频(ok={ok})") - except Exception as e: - error_msg = f"执行action1方法失败: {str(e)}" - self.log_message.emit(error_msg) - logger.error(error_msg) - import traceback - traceback_str = traceback.format_exc() - logger.error(traceback_str) - self.log_message.emit(f"错误详情: {traceback_str}") - self.finished.emit(False, error_msg) - return + # 单个上传模式,使用action方法 + # 从预查找的文件中提取文件夹路径(取第一个文件的父目录的父目录,回到最外层文件夹) + first_file = self.prepared_files[0] + if first_file['path'].is_file(): + # 文件路径:最外层文件夹/多多ID文件夹/文件名 + # 需要回到最外层文件夹 + folder_path = str(first_file['path'].parent.parent) else: - # 单个上传模式,使用action方法 - # 从预查找的文件中提取文件夹路径(取第一个文件的父目录的父目录,回到最外层文件夹) - if self.prepared_files: - first_file = self.prepared_files[0] - if first_file['path'].is_file(): - # 文件路径:最外层文件夹/多多ID文件夹/文件名 - # 需要回到最外层文件夹 - folder_path = str(first_file['path'].parent.parent) - else: - # 文件夹路径:最外层文件夹/多多ID文件夹/文件夹名 - folder_path = str(first_file['path'].parent.parent) + # 文件夹路径:最外层文件夹/多多ID文件夹/文件夹名 + folder_path = str(first_file['path'].parent.parent) - self.log_message.emit(f"使用文件夹路径: {folder_path}") - try: - result = pdd.action(folder_path=folder_path, collect_all_videos=False) - ok = bool(result.get("ok")) if isinstance(result, dict) else True - if not ok: - self.log_message.emit(f"发布校验失败: {result.get('reason') if isinstance(result, dict) else ''}") - # 单条回传结果 - if isinstance(result, dict): - self.item_result.emit({ - "user_id": config.get("多多id", ""), - "index": config.get("序号", ""), - "name": config.get("标题", "") or "", - "ok": ok, - "reason": result.get("reason", ""), - }) - self.progress.emit(100) - self.finished.emit(ok, f"单个任务执行完成(ok={ok})") - except Exception as e: - error_msg = f"执行action方法失败: {str(e)}" - self.log_message.emit(error_msg) - logger.error(error_msg) - import traceback - traceback_str = traceback.format_exc() - logger.error(traceback_str) - self.log_message.emit(f"错误详情: {traceback_str}") - self.finished.emit(False, error_msg) - return + self.log_message.emit(f"使用文件夹路径: {folder_path}") + try: + result = pdd.action(folder_path=folder_path, collect_all_videos=False) + ok = bool(result.get("ok")) if isinstance(result, dict) else True + if not ok: + self.log_message.emit(f"发布校验失败: {result.get('reason') if isinstance(result, dict) else ''}") + # 单条回传结果 + if isinstance(result, dict): + self.item_result.emit({ + "user_id": config.get("多多id", ""), + "index": config.get("序号", ""), + "name": config.get("标题", "") or "", + "ok": ok, + "reason": result.get("reason", ""), + }) + self.progress.emit(100) + self.finished.emit(ok, f"单个任务执行完成(ok={ok})") + except Exception as e: + error_msg = f"执行action方法失败: {str(e)}" + self.log_message.emit(error_msg) + logger.error(error_msg) + import traceback + traceback_str = traceback.format_exc() + logger.error(traceback_str) + self.log_message.emit(f"错误详情: {traceback_str}") + self.finished.emit(False, error_msg) + return else: # 未预查找文件,使用原来的逻辑 folder_path = config.get('文件夹路径', '').strip() @@ -1578,8 +1538,8 @@ class MainWindow(QMainWindow): def _set_status_item(self, row, text): """设置状态列图标与文本""" try: - # 临时断开 itemChanged 信号,防止递归 - self.config_table.itemChanged.disconnect(self.on_table_item_changed) + # 使用 blockSignals 临时阻止信号,防止递归 + self.config_table.blockSignals(True) try: item = QTableWidgetItem(text) item.setTextAlignment(Qt.AlignCenter) # 居中对齐 @@ -1596,15 +1556,12 @@ class MainWindow(QMainWindow): item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView)) self.config_table.setItem(row, 8, item) finally: - # 重新连接信号 - self.config_table.itemChanged.connect(self.on_table_item_changed) + # 恢复信号 + self.config_table.blockSignals(False) except Exception as e: logger.warning(f"设置状态项失败: {e}") - # 确保信号重新连接 - try: - self.config_table.itemChanged.connect(self.on_table_item_changed) - except: - pass + # 确保信号恢复 + self.config_table.blockSignals(False) def _set_progress_item(self, row, status_text): """设置进度列""" @@ -2397,8 +2354,8 @@ class MainWindow(QMainWindow): # 3. 如果行可见,更新 UI if row_idx >= 0 and row_idx < self.config_table.rowCount(): - # 临时断开 itemChanged 信号,防止递归 - self.config_table.itemChanged.disconnect(self.on_table_item_changed) + # 使用 blockSignals 临时阻止信号,防止递归 + self.config_table.blockSignals(True) try: item = QTableWidgetItem(str(value)) item.setTextAlignment(Qt.AlignCenter) @@ -2407,12 +2364,10 @@ class MainWindow(QMainWindow): item.setBackground(QColor("#E6F4FF")) self.config_table.setItem(row_idx, col, item) finally: - self.config_table.itemChanged.connect(self.on_table_item_changed) + self.config_table.blockSignals(False) except Exception as e: logger.warning(f"更新单元格失败: {e}") - try: - self.config_table.itemChanged.connect(self.on_table_item_changed) - except: pass + self.config_table.blockSignals(False) def _update_table_status(self, index, status, is_config_index=False): """更新状态列及其对应的表格单元格 @@ -2434,7 +2389,8 @@ class MainWindow(QMainWindow): # 3. 如果可见,更新 UI if row_idx >= 0 and row_idx < self.config_table.rowCount(): - self.config_table.itemChanged.disconnect(self.on_table_item_changed) + # 使用 blockSignals 临时阻止信号,防止递归 + self.config_table.blockSignals(True) try: # 第8列是"情况"列,颜色与图标与 _set_status_item 一致 status_item = QTableWidgetItem(status) @@ -2456,12 +2412,10 @@ class MainWindow(QMainWindow): status_item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView)) self.config_table.setItem(row_idx, 8, status_item) finally: - self.config_table.itemChanged.connect(self.on_table_item_changed) + self.config_table.blockSignals(False) except Exception as e: logger.error(f"更新状态失败: {e}") - try: - self.config_table.itemChanged.connect(self.on_table_item_changed) - except: pass + self.config_table.blockSignals(False) def _highlight_color(self): @@ -2492,26 +2446,6 @@ class MainWindow(QMainWindow): return -1 return self._filter_model_columns[k] - def _filter_column_index(self): - """筛选项下拉对应的表格列索引;-1 表示全部列。排除情况列,以多多ID等为筛选项。""" - i = self.table_column_filter.currentIndex() - if i <= 0 or not getattr(self, "_filter_table_columns", None): - return -1 - k = i - 1 - if k >= len(self._filter_table_columns): - return -1 - return self._filter_table_columns[k] - - def _filter_model_column_index(self): - """Model/View 模式下筛选项对应的列索引;-1 表示全部列。""" - i = self.table_column_filter.currentIndex() - if i <= 0 or not getattr(self, "_filter_model_columns", None): - return -1 - k = i - 1 - if k >= len(self._filter_model_columns): - return -1 - return self._filter_model_columns[k] - def _show_all_rows(self): """显示全部行""" for row in range(self.config_table.rowCount()): @@ -3895,6 +3829,44 @@ class MainWindow(QMainWindow): self.execute_btn.setEnabled(True) self.progress_bar.setVisible(False) + def _cleanup_worker_thread(self): + """安全清理工作线程""" + if not self.worker_thread: + return + + try: + # 如果线程正在运行,请求停止 + if self.worker_thread.isRunning(): + # 请求停止(优雅方式) + self.worker_thread.stop() + self.worker_thread.requestInterruption() + # 等待线程结束,设置合理超时 + if not self.worker_thread.wait(2000): + logger.warning("工作线程未能在2秒内停止") + # 不使用 terminate(),继续处理 + + # 安全断开所有信号连接 + try: + self.worker_thread.finished.disconnect() + except (TypeError, RuntimeError): + pass # 信号未连接或已断开 + try: + self.worker_thread.log_message.disconnect() + except (TypeError, RuntimeError): + pass + try: + self.worker_thread.progress.disconnect() + except (TypeError, RuntimeError): + pass + try: + self.worker_thread.item_result.disconnect() + except (TypeError, RuntimeError): + pass + except Exception as e: + logger.warning(f"清理工作线程时出错: {e}") + finally: + self.worker_thread = None + def _process_next_batch_task(self): """处理任务队列中的下一个任务(异步,不阻塞GUI)""" # 检查是否还有任务 @@ -3923,33 +3895,7 @@ class MainWindow(QMainWindow): # 根据任务类型处理 try: # 清理旧线程(如果存在) - try: - if self.worker_thread: - try: - if self.worker_thread.isRunning(): - # 尽量优雅停止,避免 terminate 导致 Qt 对象被强杀后崩溃 - try: - self.worker_thread.stop() - except Exception: - pass - try: - self.worker_thread.requestInterruption() - except Exception: - pass - self.worker_thread.wait(1500) - self.worker_thread.finished.disconnect() - self.worker_thread.log_message.disconnect() - self.worker_thread.progress.disconnect() - try: - self.worker_thread.item_result.disconnect() - except Exception: - pass - except Exception: - pass - self.worker_thread = None - except Exception as e: - logger.warning(f"清理旧线程时出错: {e}") - self.worker_thread = None + self._cleanup_worker_thread() # 验证 files 参数 if not files or not isinstance(files, list): @@ -4304,27 +4250,8 @@ class MainWindow(QMainWindow): ) if reply == QMessageBox.Yes: self._is_closing = True - try: - if self.worker_thread: - # 停止线程 - self.worker_thread.stop() - # 等待线程结束,设置超时避免无限等待 - if not self.worker_thread.wait(3000): # 等待3秒 - logger.warning("线程未能及时停止,强制终止") - self.worker_thread.terminate() - self.worker_thread.wait(1000) # 再等待1秒 - - # 断开信号连接 - try: - self.worker_thread.finished.disconnect() - self.worker_thread.log_message.disconnect() - self.worker_thread.progress.disconnect() - except Exception: - pass - - self.worker_thread = None - except Exception as e: - logger.error(f"关闭线程时出错: {e}") + # 使用统一的线程清理方法 + self._cleanup_worker_thread() event.accept() else: event.ignore() diff --git a/main.py b/main.py index 5671f15..dd70699 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import os import re import json import time +import threading from pathlib import Path from loguru import logger @@ -10,6 +11,29 @@ from curl_cffi import requests from DrissionPage import ChromiumPage, ChromiumOptions, SessionPage +class ThreadSafeDict: + """线程安全的字典包装类""" + def __init__(self): + self._dict = {} + self._lock = threading.Lock() + + def get(self, key, default=None): + with self._lock: + return self._dict.get(key, default) + + def __setitem__(self, key, value): + with self._lock: + self._dict[key] = value + + def __getitem__(self, key): + with self._lock: + return self._dict[key] + + def __contains__(self, key): + with self._lock: + return key in self._dict + + class Pdd: def __init__(self, url, user_id, time_start, ht, index, title=None): self.url = url @@ -1534,7 +1558,7 @@ class Pdd: return False -datas = {} +datas = ThreadSafeDict() if __name__ == '__main__': url = "18 【运动男孩都爱这么穿吗?🏃 - Liu_烫烫 | 小红书 - 你的生活兴趣社区】 😆 D13BaPl6xyUAuQO 😆 https://www.xiaohongshu.com/discovery/item/678ceeef000000001602fb54?source=webshare&xhsshare=pc_web&xsec_token=ABe9oWR9CYCsHBkWUPuoS1Fz3_Uz4WGFMdfCGwSbl0Dfs=&xsec_source=pc_share"