From 988a5c6337f7152d459a9b53e9d3eac68271f783 Mon Sep 17 00:00:00 2001 From: muyusajiangtian <3024297095@qq.com> Date: Wed, 29 Apr 2026 01:07:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(=E7=95=8C=E9=9D=A2):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=96=87=E4=BB=B6=E9=A2=84=E8=A7=88=E5=92=8C=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加扫描线程类用于文件扫描,实现文件预览表格展示 新增全选/取消全选功能,支持选择特定文件进行删除 优化主界面布局,增加预览和执行删除按钮 --- images/main.ui | 248 ++++++++++++++++++++++++++++++++------------ main.py | 185 ++++++++++++++++++++++++++++----- utils/scanThread.py | 185 +++++++++++++++++++++++++++++++++ 3 files changed, 526 insertions(+), 92 deletions(-) create mode 100644 utils/scanThread.py diff --git a/images/main.ui b/images/main.ui index cfea79d5b..bc8ad450d 100644 --- a/images/main.ui +++ b/images/main.ui @@ -6,8 +6,8 @@ 0 0 - 499 - 466 + 600 + 550 @@ -34,7 +34,7 @@ - 40 + 20 20 @@ -47,7 +47,7 @@ - 40 + 20 20 @@ -61,7 +61,7 @@ 20 - 40 + 20 @@ -74,7 +74,7 @@ 20 - 40 + 20 @@ -127,56 +127,16 @@ - - - Qt::Vertical - - - - 20 - 40 - - - - - - + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 200 - 200 - - - - .QLabel { - image: url(:/icon/wechat.png); -} - + - - - - Qt::AlignCenter + 全选 - + Qt::Horizontal @@ -191,14 +151,109 @@ - + + + + 0 + 200 + + + + .QTableWidget { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; +} +QTableWidget::item { + padding: 5px; +} +QTableWidget::item:selected { + background-color: #1890ff; +} +QHeaderView::section { + background-color: #f0f0f0; + padding: 5px; + border: 1px solid #d9d9d9; + font-weight: bold; +} + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + Qt::SolidLine + + + 3 + + + 150 + + + true + + + 50 + + + false + + + true + + + false + + + 30 + + + true + + + 30 + + + false + + + false + + + + 选择 + + + + + 文件路径 + + + + + 大小 + + + + + + Qt::Vertical 20 - 40 + 10 @@ -215,7 +270,7 @@ - 40 + 20 20 @@ -277,7 +332,7 @@ QProgressBar::chunk { - 40 + 20 20 @@ -286,10 +341,7 @@ QProgressBar::chunk { - - - 0 - + @@ -300,12 +352,80 @@ QProgressBar::chunk { - 100 + 20 20 + + + + + 0 + 35 + + + + .QLabel { + color: rgba(0,0,0,.65); + background-color: #fff; + border: 1px solid #d9d9d9; + border-right: 0px; + height: 24px; + font-size: 14px; + padding: 0px 10px; +} +.QLabel:hover { + color: #40a9ff; + background-color: #fff; + border: 1px solid #40a9ff; + height: 24px; + font-size: 14px +} + + + 预览 + + + Qt::AlignCenter + + + + + + + + 0 + 35 + + + + .QLabel { + color: rgba(0,0,0,.65); + background-color: #fff; + border: 1px solid #d9d9d9; + border-right: 0px; + height: 24px; + font-size: 14px; + padding: 0px 10px; +} +.QLabel:hover { + color: #40a9ff; + background-color: #fff; + border: 1px solid #40a9ff; + height: 24px; + font-size: 14px +} + + + 执行删除 + + + Qt::AlignCenter + + + @@ -327,7 +447,7 @@ QProgressBar::chunk { } - <html><head/><body><p>开始</p></body></html> + 一键清理 Qt::AlignCenter @@ -361,7 +481,7 @@ QProgressBar::chunk { } - <html><head/><body><p>设置</p></body></html> + 设置 Qt::AlignCenter @@ -395,7 +515,7 @@ QProgressBar::chunk { } - <html><head/><body><p>退出</p></body></html> + 退出 Qt::AlignCenter @@ -412,7 +532,7 @@ QProgressBar::chunk { - 100 + 20 20 @@ -428,7 +548,7 @@ QProgressBar::chunk { 20 - 40 + 10 diff --git a/main.py b/main.py index 7b4bf31a8..1341a025a 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsDropShadowEffect, QListWidgetItem, QListView, QWidget, \ - QLabel, QHBoxLayout, QFileDialog + QLabel, QHBoxLayout, QFileDialog, QTableWidgetItem, QHeaderView from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QThread, pyqtSignal, QMutex, QSize, QEvent, QPoint, QTimer from PyQt5.QtGui import QMouseEvent, QCursor, QColor from PyQt5.uic import loadUi @@ -15,25 +15,21 @@ from utils.multiDeleteThread import multiDeleteThread from utils.selectVersion import * from utils.selectVersion import check_dir, existing_user_config -# 设置应用程序在高DPI屏幕上启用高DPI缩放。Set the application to enable high DPI scaling on high DPI screens -# 注意事项:此行代码必须在QApplication实例化之前调用,否则会调用失败。Notes: This line of code must be called before the instantiation of the QApplication object; otherwise, it will fail +from utils.scanThread import ScanThread + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) -# determine if application is a script file or frozen exe if getattr(sys, 'frozen', False): working_dir = os.path.dirname(os.path.realpath(sys.executable)) elif __file__: working_dir = os.path.split(os.path.realpath(__file__))[0] -# 主窗口 class Window(QMainWindow): def mousePressEvent(self, event): - # 重写一堆方法使其支持拖动 if event.button() == Qt.LeftButton: self.m_drag = True self.m_DragPosition = event.globalPos() - self.pos() event.accept() - # self.setCursor(QCursor(Qt.OpenHandCursor)) def mouseMoveEvent(self, QMouseEvent): try: @@ -45,29 +41,22 @@ def mouseMoveEvent(self, QMouseEvent): def mouseReleaseEvent(self, QMouseEvent): self.m_drag = False - # self.setCursor(QCursor(Qt.ArrowCursor)) def _frame(self): - # 边框 self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground, True) - # 阴影 effect = QGraphicsDropShadowEffect(blurRadius=12, xOffset=0, yOffset=0) effect.setColor(QColor(25, 25, 25, 170)) self.mainFrame.setGraphicsEffect(effect) def doFadeIn(self): - # 动画 self.animation = QPropertyAnimation(self, b'windowOpacity') - # 持续时间250ms self.animation.setDuration(250) try: - # 尝试先取消动画完成后关闭窗口的信号 self.animation.finished.disconnect(self.close) except: pass self.animation.stop() - # 透明度范围从0逐渐增加到1 self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setStartValue(0) self.animation.setEndValue(1) @@ -75,9 +64,7 @@ def doFadeIn(self): def doFadeOut(self): self.animation.stop() - # 动画完成则关闭窗口 self.animation.finished.connect(self.close) - # 透明度范围从1逐渐减少到0s self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setStartValue(1) self.animation.setEndValue(0) @@ -94,7 +81,7 @@ def setWarninginfo(self, text): background: #fff2f0; } """) - self.lab_info.setWordWrap(True) # 启用自动换行 + self.lab_info.setWordWrap(True) self.lab_info.setText(text) def setSuccessinfo(self, text): @@ -108,7 +95,7 @@ def setSuccessinfo(self, text): background: #f6ffed; } """) - self.lab_info.setWordWrap(True) # 启用自动换行 + self.lab_info.setWordWrap(True) self.lab_info.setText(text) @@ -133,7 +120,6 @@ def open_file(self): elem for elem in list_ if elem != 'All Users' and elem != 'Applet' and elem != 'WMPF' ] - # 如果已有用户配置,那么写入新的用户配置,否则默认写入新配置 dir_list = [] user_config = [] existing_user_config_dic = existing_user_config() @@ -282,6 +268,9 @@ def deal_emit_slot(self, set_status): self.config_exists = True def closeEvent(self, event): + if hasattr(self, 'scan_thread') and self.scan_thread.isRunning(): + self.scan_thread.stop() + self.scan_thread.wait() sys.exit(0) def eventFilter(self, object, event): @@ -291,7 +280,7 @@ def eventFilter(self, object, event): return True elif object == self.lab_clean: try: - self.setSuccessinfo("正在清理中...") + self.setSuccessinfo("正在一键清理中...") self.justdoit() except: self.setWarninginfo("清理失败,请检查配置文件后重试") @@ -300,18 +289,158 @@ def eventFilter(self, object, event): cw = ConfigWindow() cw.Signal_OneParameter.connect(self.deal_emit_slot) return True + elif object == self.lab_preview: + try: + self.start_preview() + except Exception as e: + self.setWarninginfo(f"预览失败:{str(e)}") + return True + elif object == self.lab_execute_delete: + try: + self.execute_delete() + except Exception as e: + self.setWarninginfo(f"删除失败:{str(e)}") + return True return False def _eventfilter(self): - # 事件过滤 self.lab_close.installEventFilter(self) self.lab_clean.installEventFilter(self) self.lab_config.installEventFilter(self) + self.lab_preview.installEventFilter(self) + self.lab_execute_delete.installEventFilter(self) + + def init_table(self): + self.table_files.setColumnCount(3) + self.table_files.setHorizontalHeaderLabels(["选择", "文件路径", "大小"]) + self.table_files.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) + self.table_files.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) + self.table_files.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) + self.table_files.setColumnWidth(0, 50) + self.table_files.setColumnWidth(2, 100) + self.table_files.setRowCount(0) + self.file_data = [] + + def clear_table(self): + self.table_files.setRowCount(0) + self.file_data = [] + self.check_select_all.setChecked(False) + + def add_file_to_table(self, file_path, file_size, file_type): + row = self.table_files.rowCount() + self.table_files.insertRow(row) + + checkbox_item = QTableWidgetItem() + checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) + checkbox_item.setCheckState(Qt.Checked) + self.table_files.setItem(row, 0, checkbox_item) + + path_item = QTableWidgetItem(file_path) + path_item.setToolTip(file_path) + self.table_files.setItem(row, 1, path_item) + + size_item = QTableWidgetItem(file_size) + self.table_files.setItem(row, 2, size_item) + + self.file_data.append({"path": file_path, "type": file_type}) + + def start_preview(self): + if not os.path.exists(working_dir + "/config.json"): + self.setWarninginfo("请先配置微信数据目录") + return + + if hasattr(self, 'scan_thread') and self.scan_thread.isRunning(): + self.setWarninginfo("正在扫描中,请稍候...") + return + + self.clear_table() + self.bar_progress.setValue(0) + self.setSuccessinfo("正在扫描文件,请稍候...") + + fd = open(working_dir + "/config.json", encoding="utf-8") + config = json.load(fd) + + self.scan_thread = ScanThread(config) + self.scan_thread.scan_progress_signal.connect(self.on_scan_progress) + self.scan_thread.scan_file_found_signal.connect(self.on_scan_file_found) + self.scan_thread.scan_finished_signal.connect(self.on_scan_finished) + self.scan_thread.scan_error_signal.connect(self.on_scan_error) + self.scan_thread.start() + + def on_scan_progress(self, progress): + self.bar_progress.setValue(progress) + + def on_scan_file_found(self, file_path, file_size, file_type): + self.add_file_to_table(file_path, file_size, file_type) + + def on_scan_finished(self, file_count, dir_count): + total = file_count + dir_count + if total == 0: + self.setSuccessinfo("扫描完成,没有找到需要清理的文件") + else: + self.setSuccessinfo(f"扫描完成,共找到 {total} 个文件/文件夹") + self.bar_progress.setValue(100) + + def on_scan_error(self, error_msg): + self.setWarninginfo(f"扫描出错:{error_msg}") + + def toggle_select_all(self, state): + if state == Qt.Checked: + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item: + item.setCheckState(Qt.Checked) + else: + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item: + item.setCheckState(Qt.Unchecked) + + def execute_delete(self): + selected_files = [] + selected_dirs = [] + + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item and item.checkState() == Qt.Checked: + file_info = self.file_data[row] + if file_info["type"] == "file": + selected_files.append(file_info["path"]) + else: + selected_dirs.append(file_info["path"]) + + if len(selected_files) + len(selected_dirs) == 0: + self.setWarninginfo("请先选择要删除的文件") + return + + self.setSuccessinfo("正在删除选中的文件...") + + self.total_file = len(selected_files) + self.total_dir = len(selected_dirs) + + share_thread_arr = [0] + + if len(selected_files) + len(selected_dirs) > 0: + thread = multiDeleteThread(selected_files, selected_dirs, share_thread_arr) + thread.delete_process_signal.connect(self.callback) + thread.start() + + self.remove_deleted_rows(selected_files, selected_dirs) + + def remove_deleted_rows(self, deleted_files, deleted_dirs): + rows_to_remove = [] + for row in range(self.table_files.rowCount()): + file_info = self.file_data[row] + if file_info["path"] in deleted_files or file_info["path"] in deleted_dirs: + rows_to_remove.append(row) + + for row in sorted(rows_to_remove, reverse=True): + self.table_files.removeRow(row) + del self.file_data[row] def get_fileNum(self, path, day, picCacheCheck, fileCheck, picCheck, videoCheck, file_list, dir_list): dir_name = PureWindowsPath(path) - # Convert path to the right format for the current operating system correct_path = Path(dir_name) now = datetime.datetime.now() if picCacheCheck: @@ -353,12 +482,10 @@ def pathFileDeal(self, now, day, path, file_list, dir_list): def getPathFileNum(self, now, day, path_one, path_two, file_list, dir_list): - # caculate path_one self.pathFileDeal(now, day, path_one, file_list, dir_list) td = datetime.datetime.now() - datetime.timedelta(days=day) td_year = td.year td_month = td.month - # caculate path_two if os.path.exists(path_two): osdir = os.listdir(path_two) dirlist = [] @@ -432,7 +559,7 @@ def justdoit(self): self.total_file = total_file self.total_dir = total_dir for thread in thread_list: - thread.run() + thread.start() def show_config_window(self): self.config_window = ConfigWindow() @@ -444,20 +571,22 @@ def __init__(self): self._frame() self._eventfilter() + self.init_table() + + self.check_select_all.stateChanged.connect(self.toggle_select_all) + self.doFadeIn() self.config_exists = True self.show() - # 判断配置文件是否存在 if not os.path.exists(working_dir + "/config.json"): self.setWarninginfo("首次使用,即将自动弹出配置窗口") self.config_exists = False timer = QTimer(self) timer.timeout.connect(self.show_config_window) - timer.setSingleShot(True) # 只执行一次 + timer.setSingleShot(True) - # 设置定时器的时间间隔,这里设置为 1000ms(1秒) timer.start(1000) diff --git a/utils/scanThread.py b/utils/scanThread.py new file mode 100644 index 000000000..c4c526d27 --- /dev/null +++ b/utils/scanThread.py @@ -0,0 +1,185 @@ +import os, datetime, re +from pathlib import Path, PureWindowsPath +from PyQt5.QtCore import QThread, pyqtSignal, QMutex + + +class ScanThread(QThread): + scan_progress_signal = pyqtSignal(int) + scan_file_found_signal = pyqtSignal(str, str, str) + scan_finished_signal = pyqtSignal(int, int) + scan_error_signal = pyqtSignal(str) + + def __init__(self, config): + super(ScanThread, self).__init__() + self.config = config + self.is_running = True + self.qmut = QMutex() + + def stop(self): + self.is_running = False + + def get_file_size_str(self, size): + if size < 1024: + return f"{size} B" + elif size < 1024 * 1024: + return f"{size / 1024:.2f} KB" + elif size < 1024 * 1024 * 1024: + return f"{size / (1024 * 1024):.2f} MB" + else: + return f"{size / (1024 * 1024 * 1024):.2f} GB" + + def get_file_size(self, path): + try: + if os.path.isfile(path): + return os.path.getsize(path) + elif os.path.isdir(path): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(path): + for f in filenames: + fp = os.path.join(dirpath, f) + try: + total_size += os.path.getsize(fp) + except: + pass + return total_size + except: + pass + return 0 + + def get_fileNum(self, path, day, picCacheCheck, fileCheck, picCheck, videoCheck, file_list, dir_list): + if not self.is_running: + return + + dir_name = PureWindowsPath(path) + correct_path = Path(dir_name) + now = datetime.datetime.now() + + if picCacheCheck: + path_one = correct_path / 'Attachment' + path_two = correct_path / 'FileStorage/Cache' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if fileCheck: + path_one = correct_path / 'Files' + path_two = correct_path / 'FileStorage/File' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if picCheck: + path_one = correct_path / 'Image/Image' + path_two = correct_path / 'FileStorage/Image' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if videoCheck: + path_one = correct_path / 'Video' + path_two = correct_path / 'FileStorage/Video' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + def pathFileDeal(self, now, day, path, file_list, dir_list): + if not self.is_running: + return + + if os.path.exists(path): + filelist = [ + f for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) + ] + for i in range(0, len(filelist)): + if not self.is_running: + return + + file_path = os.path.join(path, filelist[i]) + if os.path.isdir(file_path): + continue + + try: + timestamp = datetime.datetime.fromtimestamp( + os.path.getmtime(file_path)) + diff = (now - timestamp).days + if diff >= day: + file_list.append(file_path) + file_size = self.get_file_size(file_path) + file_size_str = self.get_file_size_str(file_size) + self.scan_file_found_signal.emit(file_path, file_size_str, "file") + except Exception as e: + print(f"Error processing file {file_path}: {e}") + continue + + def getPathFileNum(self, now, day, path_one, path_two, file_list, dir_list): + if not self.is_running: + return + + self.pathFileDeal(now, day, path_one, file_list, dir_list) + + td = datetime.datetime.now() - datetime.timedelta(days=day) + td_year = td.year + td_month = td.month + + if os.path.exists(path_two): + osdir = os.listdir(path_two) + dirlist = [] + for i in range(0, len(osdir)): + file_path = os.path.join(path_two, osdir[i]) + if os.path.isdir(file_path): + dirlist.append(osdir[i]) + + for i in range(0, len(dirlist)): + if not self.is_running: + return + + file_path = os.path.join(path_two, dirlist[i]) + if os.path.isfile(file_path): + continue + + if re.match('\d{4}(\-)\d{2}', dirlist[i]) != None: + cyear = int(dirlist[i].split('-', 1)[0]) + cmonth = int(dirlist[i].split('-', 1)[1]) + if self.__before_deadline(cyear, cmonth, td_year, td_month): + dir_list.append(file_path) + file_size = self.get_file_size(file_path) + file_size_str = self.get_file_size_str(file_size) + self.scan_file_found_signal.emit(file_path, file_size_str, "dir") + else: + if cmonth == td_month: + self.pathFileDeal(now, day, file_path, file_list, dir_list) + + def __before_deadline(self, cyear, cmonth, td_year, td_month): + if cyear < td_year: + return True + elif cyear > td_year: + return False + elif cyear == td_year: + return cmonth < td_month + + def run(self): + try: + file_list = [] + dir_list = [] + + i = 0 + total_users = len(self.config["users"]) + + for value in self.config["users"]: + if not self.is_running: + break + + if value["is_clean"]: + self.get_fileNum( + self.config["data_dir"][i], + int(value["clean_days"]), + value["clean_pic_cache"], + value["clean_file"], + value["clean_pic"], + value["clean_video"], + file_list, + dir_list + ) + + progress = int((i + 1) / total_users * 100) + self.scan_progress_signal.emit(progress) + i = i + 1 + + if self.is_running: + self.scan_finished_signal.emit(len(file_list), len(dir_list)) + + except Exception as e: + self.scan_error_signal.emit(str(e)) From 3e91cd1180099930723cc188ac1052cd5f788256 Mon Sep 17 00:00:00 2001 From: muyusajiangtian <3024297095@qq.com> Date: Wed, 29 Apr 2026 01:33:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E6=A3=80=E6=B5=8B=E5=BE=AE=E4=BF=A1=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增自动扫描常见路径和注册表查找微信数据目录功能 优化目录检测和用户配置创建逻辑 --- main.py | 86 ++++++++++++- .../__pycache__/deleteThread.cpython-310.pyc | Bin 0 -> 1307 bytes .../multiDeleteThread.cpython-310.pyc | Bin 0 -> 1226 bytes utils/__pycache__/resources.cpython-310.pyc | Bin 0 -> 49953 bytes utils/__pycache__/scanThread.cpython-310.pyc | Bin 0 -> 4963 bytes .../__pycache__/selectVersion.cpython-310.pyc | Bin 0 -> 2819 bytes utils/selectVersion.py | 117 +++++++++++++++--- 7 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 utils/__pycache__/deleteThread.cpython-310.pyc create mode 100644 utils/__pycache__/multiDeleteThread.cpython-310.pyc create mode 100644 utils/__pycache__/resources.cpython-310.pyc create mode 100644 utils/__pycache__/scanThread.cpython-310.pyc create mode 100644 utils/__pycache__/selectVersion.cpython-310.pyc diff --git a/main.py b/main.py index 1341a025a..95ba46d8b 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,7 @@ from utils.deleteThread import * from utils.multiDeleteThread import multiDeleteThread from utils.selectVersion import * -from utils.selectVersion import check_dir, existing_user_config +from utils.selectVersion import check_dir, existing_user_config, find_all_wechat_paths from utils.scanThread import ScanThread QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) @@ -565,6 +565,72 @@ def show_config_window(self): self.config_window = ConfigWindow() self.setSuccessinfo("已经准备好,可以开始了!") + def smart_detect_wechat_path(self): + found_paths = find_all_wechat_paths() + + if len(found_paths) > 0: + config = self.create_config_from_paths(found_paths) + self.save_config(config) + self.setSuccessinfo(f"自动检测到微信数据目录:{found_paths[0]}") + self.config_exists = True + return True + else: + return False + + def create_config_from_paths(self, paths): + config = {"data_dir": [], "users": []} + + for path in paths: + dir_list, names = get_dir_name(path) + for i, user_dir in enumerate(dir_list): + config["data_dir"].append(path) + config["users"].append({ + "wechat_id": names[i], + "clean_days": 365, + "is_clean": True, + "clean_pic_cache": True, + "clean_file": False, + "clean_pic": True, + "clean_video": True, + "is_timer": True, + "timer": "0h" + }) + + return config + + def save_config(self, config): + if len(config["data_dir"]) > 0: + with open(working_dir + "/config.json", "w", encoding="utf-8") as f: + json.dump(config, f) + return True + return False + + def prompt_select_wechat_path(self): + self.setWarninginfo("未检测到微信数据目录,请手动选择") + + folder_path = QFileDialog.getExistingDirectory( + self, + "请选择微信数据目录(通常是 'WeChat Files' 文件夹)", + "" + ) + + if folder_path and folder_path != '': + if check_dir(folder_path) == 0: + self.setSuccessinfo(f"已选择微信数据目录:{folder_path}") + config = self.create_config_from_paths([folder_path]) + if self.save_config(config): + self.config_exists = True + return True + else: + self.setWarninginfo("保存配置失败") + return False + else: + self.setWarninginfo("选择的目录不是有效的微信数据目录,请重新选择") + return False + else: + self.setWarninginfo("未选择目录,部分功能可能无法使用") + return False + def __init__(self): super().__init__() loadUi(working_dir + "/images/main.ui", self) @@ -580,14 +646,22 @@ def __init__(self): self.show() if not os.path.exists(working_dir + "/config.json"): - self.setWarninginfo("首次使用,即将自动弹出配置窗口") + self.setSuccessinfo("正在智能检测微信数据目录...") self.config_exists = False - + timer = QTimer(self) - timer.timeout.connect(self.show_config_window) - timer.setSingleShot(True) - timer.start(1000) + def check_and_prompt(): + if self.smart_detect_wechat_path(): + self.setSuccessinfo("检测成功!已经准备好,可以开始了!") + else: + self.prompt_select_wechat_path() + + timer.timeout.connect(check_and_prompt) + timer.setSingleShot(True) + timer.start(500) + else: + self.setSuccessinfo("已经准备好,可以开始了!") if __name__ == '__main__': diff --git a/utils/__pycache__/deleteThread.cpython-310.pyc b/utils/__pycache__/deleteThread.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19bd4b18a82bc5836261a3fb46baec2a6f43ff1b GIT binary patch literal 1307 zcmbtUON$#v5bno3wUWFsavTUGKIAgN_+VlOLI{D4@x|U`*}#%!Lo=D~wMLm4X}f#y zYSD&(7W_BXNB<1}2_O0z2<**gUy`aBi^%7+=&!3^Rn^~DqxonwAkbd_{kHr%B;+rg zY_|X=-$S>*0VtwqMMOVRLAyBMi0ecobt+Oybh;*D z@R+DTg_lHyI$m?Jqv#>YqHBml_A}}SRyXRtGr65++{Xt$PmRu%A1s!q?y!8;QNc9V!J3Nj1v#OLDt4KONO2Wh5)r>30TBs=5B*T-N;}<0 zD>k0cA=;a6AHu{DM^|LcuGmXL$qH=AR}3(4;fk&)AY0Sh1Xd!2yRjk=gY6p%x3k2D zwq0mrBAEF^%Cae)ls-8rEB&Oj&c~`Woxv;J_!!ss+uFxY*9$O!mcZ)j#H8TBppLJo zPo*rX+*&DLk#}D``sTQpD{by9%>0?j>*Kvj=grgQkuIjWJ8oTB+2idO-&-tA0-koE zTXZ?55&Uk_0X@IzD8?419&A~W^_`Sr64>Nv6kjFDNO zdVT`5fiE(P*7Q>-p9#4DCTH}v&(jeT%hx6TLEy!gErgTcBP0eD_E z&J59-^z=!PLW4fGui-$~rR&5KQQM#gAL=t*NS{7BEA+yZ^Tvk@18%awjL}hwg&-HX zn~JD+=BYLxVaN_{c5zMH+y=vb0gzAz0w#1wCnzP`SP2``G5we(f3knhcR!F9u4gG$ zWs}z$s&XJ@Jy$IRmVPMZX`5I5jzP^0hBBWZVD*^K0sQEFDHgh_q})pI1|GpIyV6IR zEZ#~W&mr55yVY#y+1&a>?sOSUSQJg@zca@!4FCWD literal 0 HcmV?d00001 diff --git a/utils/__pycache__/multiDeleteThread.cpython-310.pyc b/utils/__pycache__/multiDeleteThread.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48e94ec52533286cb2f1d02ce93550126d0daf5d GIT binary patch literal 1226 zcmb_b&1xJ+5U&22+1b@fR*Y=mnESQBHMTh1ZUDRG{_*awpr$OH5>LW*xW=aN*-SVW*}y6KPVuIj4ls;|e3ivz&% z+u>jNl>p!m+MFB`H(%hYuOVpAtbmdwtmFwVMIsoT@j{j|kqnHOLlS%mT4?zcv@~ok zl28X55Qp#ZKE%TJ*6)q1NwptLrrT~Ke^8}`57%!u&ODBpAJnGOSDa1jQARgT4)K0d ze}aO6glR~)5k}5g52&J<*m39!kvuxPW~46zg~$&^Fi$5%K&2?02?3 zvfY+=8vPG`K6ZUAD2w~Z!wu}V8%(zt>~aP>3$8!oo$f2t?(%Or*>ut)_6TCRX6d=_ z2%3us2d|zSy!!R%_1mLgUK~FE`Op3DkKX)n_~TCpufK~Ix-Q`#0WY`9#(7zcvv1mt z`E$A@mcj;Q2HRG5w3epKoe!I;wWLz|U}E#i`TotvnVGnJT*bl;Nzo_5s7dNZo*8=v zk2+W;rLax4fnzsdB6v zjH>)lsqH2$I*p~qdx3dc3fi*BQhog5dtPRyD3m&pC6UZhs_j3Zm$sCPAYwzl+-pzQ z;+`Mgovyp9m)G6u*c!{}0tK`5|18zko+Y5qbi#XEjkXZc^)BVlsb_776&SKGSP}mM D`)eW` literal 0 HcmV?d00001 diff --git a/utils/__pycache__/resources.cpython-310.pyc b/utils/__pycache__/resources.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec867f47e813fbc9e5b8f5296682fea6cf474dbc GIT binary patch literal 49953 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 Mvz%)=_e$>n0pzWBK>z>% literal 0 HcmV?d00001 diff --git a/utils/__pycache__/scanThread.cpython-310.pyc b/utils/__pycache__/scanThread.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..481c18057829c788e2e1828d8769a9732927526b GIT binary patch literal 4963 zcmZ`-TaO$^6|Snj&UI%mv3Fz3Y>;9yB3Zy80gUB1zC|H;WwQY*!-m$Rt7o@&=UUwx zXNR5;u@T7&5_v>Xq?yM&@CU+Q;1|>@5+XeD1_BZDo$8t0Sv&4gpE`Z2y1FjsJKw44 zRVqb>>vwevmt<1`#j^;X;M zcnz=PH~dbaQQ+(~CT;0_$fOf_M^>XK`4+3YU!qsGSr^*A+e~-$!rcQE-H*Gn_aGrd z-O}E@H+NOkl)BhI9Hd+EPPf_C?!7w)Y4l!5Z_i&eTDp?oLPU&4*btEw*+;zLpmgbn zuX|*JRWy9EqPFl6YjMe?_|V7LENOqp8YSsS7iC#`(nnd51zAK{l_gn5xgaaDin1mb zWDVt_T$D>Fmp)=PYb;}a%euG~HoN%@LSe?13(~FWom_;HvXp1+NQ}jVah5?3EJ@N% zog;?Ql?*+K%{S|wc9MgBq?CvEv>yaeYQ3*|JF3~)zR`}F-8+Z(qj0yGZXcv^ zJK0W-=d#g1R0Z@?#+~>ORqojZ3Yewf+V@vTM-pT!(n= zx|vE{m}y}i@i2OHa*I1r8nohe6i{uDr0OY}X%)ifMeg&Oxocv0es0!}y>w~`Xm98? ztNjNk!&g(b$HyY$6RxU8w@AN_^*2wpsd+{6kx1Ftp{JYd?e%SIWR2{RlR0==9l55h zn^~DV5hHI{%+MPBTA4TDKVTzqpAEjr;mMr61#NIHgJp}|$8K*KE7?s95Sxlz0$we8 ziMIO`2i!QJbC3Mh-IwP%g(2^*C%OI^$mE6=e}8Kpp3 z$!J$fnaIjnCG#?W;v!zy>N4u#s-~U&#CDF_L_25N&V6D#S4p(58_dr*%twbaAnazVvy?Wv@+LFyfUFpZ68QqQDDBREe zBv*Z^F~;J%^!d34K|4-Td@5FR8hMv4hdrgDFb&B66h(FIlN$88(Ns$x^d5D-(#-iV z#P8vq36{>|pvvHL)K!A-GvJ$j1zE~SC zW`0(d_Qc`{CW8w$OP3Dc=YR!g!&@(0lwL+jl{ri={g}i*gADB@a z$MrT(u@kuw#mi+{CfdOr4CU>G5jW9o8sjigJ`tE2qUa}L02;`HyKMrtqL8G{l0ZfU z%GP4zyNZI!=RB`7q-OZ6c!iuP>~;EAiJubUQsgRVrBHaVOfb>dVPZoL;q{WR`%wbNH|H=Ti$)?PhF!YteTE*d46s0z?@uxAP|-XF|Ye@$)*HnNtD3? zLbwFXheZR>BJ>)!Kd55Y%H01zd}aT`c7|u4uyY}0ZL^C~nPYl}8_%HNm{d7$@VSvMY^0nk5}kmFcDI$QKYFpW_OHz0qgNpR?QZG|H8ypf682p5Mwp5 z;tXk{8rq!AYS0!DD^|yAAjn1OQmhzk;myLnP+uIcYh|{sgRNs4^EQ!qUd5&uEDfvM z^4H%VJ-7Wr9m=JV<}ZASJz^k_Sk<4-Vf0gKP0+{#zHvhtwbQ1y52Gfq)aiA>2FGYq zKcu!lB5|3-k4aFV$U*iBX%sxPP$11tGYxmO1z;fQ@3(=N^TCN@1;IN}tEZws;)K+W zyU_{m)HUkpRfzghPJ1VaB9>8?NSwe*q*8mS49u8)C}!B&>6yffk}ATSx+&(axd z^Ud3NOn36ddmBBdUy*o=M1#cXiR#72cH8s#zw&#s; zM4+@5QXGLUiHQkkxddo1b_a|pd+Sj`-k!LL(JGjN+$wQwj;cB2{HDe`{3Xx_rfy{Be0XaQ8Sv&=CX4a^#) z{8W=q)p@tsKA*26EhcR?nU!i{)D8Y+!9lZ~*C>LVSsg`3 zU)3gYjl>=aiUH<714X1~a)UftnME~GltP8NPof8*E7N}{!l>O2g1Na;E^Bz(`~%|R zDLD_}MJ-nB` sv~e%JfxxQhAEkU^^cmC3+(69on*_qYk}t|t;s^7O$SSKLAy~2g58i^8GXMYp literal 0 HcmV?d00001 diff --git a/utils/__pycache__/selectVersion.cpython-310.pyc b/utils/__pycache__/selectVersion.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a89a81d73a86de4ea76db3a66a0a24560dbf218b GIT binary patch literal 2819 zcmb_e&2J>d6|buPn4a<2yK7Kv5{M>|2nD5uj)%ozG^o|;s+n!O z-94`ES?`Q`5RzB9<&awr9PA4ce+K_XU6GKuaYl;B?^WB2?VKXft*UzU_3G98_`S*( z7h4S9*WZ4hyt=^HH`KZOdFZ@{QtYFWO!AcV>z4Pq?Tfy!teBw6^^6wiScMO{EBH(*bjf+S|7x@RJt?P`AA2H zgUwV$*~96cil0Q~U{WS&F(_23;__o0ndI5pcxsyQ6BQqXVBQ3mB0$yRtK8+T&~0>L zloy{5%0;pF{X2+~?ekMnI+Cy8*7~i|t!dKlKgFDt-l<>fqvFzih0R66=1wK!~ zJhm4M6WMKOlF+zTJ-&Jd{S;>5IM)YQPj@tKK317=iKTJVJd$1Ch@muoeU@1up5b}N z*Q%JLWnWBUeFKcGqU#Et$;`|41Y&(@*DcHxFQdB2Z}JuopvnM$t5)eRF%gs7Tzn)j z+0Ux<-ymiy(lM*=z~BoU&PYC;UWkwr6+Gz0W-2A+hE&@0cI6uDd;3*;OOx zR8KnxYTC*2vNOylnOvJK?BmtzQCrVUu$ktCvPh@pah!|lG%e6xlaj;q=q*}Xe z8o-xBm6g{u+1enmiu1$q-P_nw5QjI}tTR$2#ig~!+D4kkQCi#uak1D!VK=aGtsB^6 zJRM~t*e+F>zDWb_ex78;OA3l_KN`ceD5?VjN(%%HOb|okk<|+ebcQFIu>cTh}#I) zv7=S#&t2Jo5BUIJ??lY~%GZx7Uj{JsBfGy*`Pf_20cM);xdw2eK{x`AXnXME5wqUa z+MYEx*9SJ64Q4BYdwDz|M<@n+Y7_aVL*ZM@{xLwJyBC$w;6t+YU?++XqLC^F9|6{} zyk7iaa+s9BkYTR(l1%2u#hu$>Arg^M)IBW?VAy@*I+k~4gta!EY1(aIlVI1 z>RR_)j+6gOj#{e6dpkd?5M8G)#&JKuErE-7ZufmqljHGhrO1cnaRk}RS}TJ(#r4~y z#9s7N?I8cp79LJ7w7foRUsf}qwgE>TM@7*mg3?nRLKHEYQj~RG3sm|Jbtx+KZ%}mu zYw!(2}A912Z4438#JN&y~* zdV}Pmta_=UjIG~AMWrCz3Rqj%k?xZI3(Zp2uGRD_qAa?2#W+n$6Ce|$7EN$IAq!4f wrbYBd(v%KrT@oZ%o|jIxg4hQ`uu%(qm)>?3An0j8|EdT?5Cn_C;_~8u0s5ZFKL7v# literal 0 HcmV?d00001 diff --git a/utils/selectVersion.py b/utils/selectVersion.py index 96236907a..cc1dde214 100644 --- a/utils/selectVersion.py +++ b/utils/selectVersion.py @@ -7,10 +7,15 @@ working_dir = os.path.split(os.path.realpath(__file__))[0] def check_dir(file_path): - list_ = os.listdir(file_path) - if 'All Users' in list_ or 'Applet' in list_ or 'WMPF' in list_: - return 0 - else: + if not os.path.exists(file_path): + return 1 + try: + list_ = os.listdir(file_path) + if 'All Users' in list_ or 'Applet' in list_ or 'WMPF' in list_: + return 0 + else: + return 1 + except: return 1 @@ -43,15 +48,98 @@ def read_registry_value(key_path, value_name): def get_dir_name(filepath): dirlist = [] names = [] - list_ = os.listdir(filepath) - # 换用lambda 表达式更安全,remove函数如果不存在对象会抛出异常 - list_ = [element for element in list_ if element != 'All Users' and element != 'Applet' and element != 'WMPF'] - for i in range(0, len(list_)): - file_path = os.path.join(filepath, list_[i]) - if os.path.isdir(file_path): - dirlist.append(file_path) - names.append(list_[i]) - return (dirlist, names) + if not os.path.exists(filepath): + return ([], []) + try: + list_ = os.listdir(filepath) + list_ = [element for element in list_ if element != 'All Users' and element != 'Applet' and element != 'WMPF'] + for i in range(0, len(list_)): + file_path = os.path.join(filepath, list_[i]) + if os.path.isdir(file_path): + dirlist.append(file_path) + names.append(list_[i]) + return (dirlist, names) + except: + return ([], []) + +def find_all_wechat_paths(): + """ + 智能扫描所有可能的微信数据目录位置 + 返回所有找到的有效路径列表 + """ + user = getpass.getuser() + found_paths = [] + + common_paths = [ + f'C:\\Users\\{user}\\Documents\\WeChat Files', + f'C:\\Users\\{user}\\OneDrive\\Documents\\WeChat Files', + f'D:\\Documents\\WeChat Files', + f'E:\\Documents\\WeChat Files', + f'F:\\Documents\\WeChat Files', + f'C:\\Users\\{user}\\AppData\\Local\\Packages\\TencentWeChatLimited.forWindows10_sdtnhv12zgd7a\\LocalCache\\Roaming\\Tencent\\WeChatAppStore\\WeChatAppStore Files', + f'C:\\Users\\{user}\\AppData\\Local\\Packages\\TencentWeChatLimited.WeChatUWP_sdtnhv12zgd7a\\LocalCache\\Roaming\\Tencent\\WeChatAppStore\\WeChatAppStore Files', + ] + + for drive in ['C', 'D', 'E', 'F', 'G']: + common_paths.append(f'{drive}:\\WeChat Files') + common_paths.append(f'{drive}:\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Program Files\\Tencent\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Program Files (x86)\\Tencent\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Users\\{user}\\WeChat Files') + common_paths.append(f'{drive}:\\Users\\{user}\\Tencent Files\\WeChat Files') + + for path in common_paths: + if check_dir(path) == 0: + if path not in found_paths: + found_paths.append(path) + + registry_key_paths = [ + r"software\tencent\wechat", + r"Software\Tencent\WeChat", + r"SOFTWARE\Tencent\WeChat", + ] + + for key_path in registry_key_paths: + value_names = ["FileSavePath", "InstallPath"] + for value_name in value_names: + value = read_registry_value(key_path, value_name) + if value: + if value == 'MyDocument:': + doc_path = f'C:\\Users\\{user}\\Documents\\WeChat Files' + if check_dir(doc_path) == 0 and doc_path not in found_paths: + found_paths.append(doc_path) + elif os.path.isdir(value): + wechat_path = os.path.join(value, 'WeChat Files') + if check_dir(wechat_path) == 0 and wechat_path not in found_paths: + found_paths.append(wechat_path) + if check_dir(value) == 0 and value not in found_paths: + found_paths.append(value) + + try: + for key_path in registry_key_paths: + try: + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) + i = 0 + while True: + try: + subkey_name = winreg.EnumKey(key, i) + if 'WeChat' in subkey_name or 'Wechat' in subkey_name or 'wechat' in subkey_name: + subkey_path = f"{key_path}\\{subkey_name}" + value = read_registry_value(subkey_path, "FileSavePath") + if value and os.path.isdir(value): + wechat_path = os.path.join(value, 'WeChat Files') + if check_dir(wechat_path) == 0 and wechat_path not in found_paths: + found_paths.append(wechat_path) + i += 1 + except OSError: + break + winreg.CloseKey(key) + except: + pass + except: + pass + + return found_paths class selectVersion: @@ -66,11 +154,9 @@ def getAllPath(self): if os.path.exists(dic[key]): return get_dir_name(dic[key]) - # 注册表路径和字段名 registry_key_path = r"software\tencent\wechat" value_name = "FileSavePath" - # 读取字段值 value = read_registry_value(registry_key_path, value_name) if value and value != 'MyDocument:' and os.path.isdir(value): @@ -79,4 +165,3 @@ def getAllPath(self): return get_dir_name(fpath) else: return ([], []) -