# abc **Repository Path**: with-the-flow/abc ## Basic Information - **Project Name**: abc - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-02 - **Last Updated**: 2026-05-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ```python # g_c.py import os import shutil import threading from queue import Queue import tkinter as tk from tkinter import ttk, scrolledtext, messagebox, simpledialog # ========== 配置 ========== SRC_FOLDER_NAME = '.gradle' ABC_FOLDER = 'abc' THREAD_COUNT = 16 # ========================== class GradleCopyApp: def __init__(self, root): self.root = root self.root.title("Gradle 复制工具 - 16线程") self.root.geometry("800x600") self.root.resizable(True, True) # 变量 self.src_dir = tk.StringVar() self.dst_dir = tk.StringVar(value=os.path.join(os.path.expanduser('~'), SRC_FOLDER_NAME)) self.total_files = tk.IntVar(value=0) self.done_files = tk.IntVar(value=0) self.ok_files = tk.IntVar(value=0) self.fail_files = tk.IntVar(value=0) self.speed_var = tk.StringVar(value="0 MB/s") self.is_running = False self.task_queue = Queue() self.lock = threading.Lock() self.bytes_copied = 0 # 错误记录列表: [(src, dst, error_msg, traceback_str), ...] self.error_records = [] self.build_ui() self.auto_detect() def build_ui(self): # 标题 tk.Label(self.root, text="Gradle 文件夹复制工具", font=("微软雅黑", 18, "bold")).pack(pady=8) tk.Label(self.root, text="16线程高速复制", font=("微软雅黑", 10), fg='gray').pack() # 源目录 frame_src = tk.Frame(self.root) frame_src.pack(fill='x', padx=25, pady=5) tk.Label(frame_src, text="源目录:", font=("微软雅黑", 10), width=8, anchor='e').pack(side='left') tk.Entry(frame_src, textvariable=self.src_dir, font=("Consolas", 10), width=50, state='readonly').pack(side='left', padx=5, fill='x', expand=True) tk.Button(frame_src, text="🔄 刷新", command=self.auto_detect, font=("微软雅黑", 9), width=8).pack(side='left') # 目标目录 frame_dst = tk.Frame(self.root) frame_dst.pack(fill='x', padx=25, pady=5) tk.Label(frame_dst, text="目标目录:", font=("微软雅黑", 10), width=8, anchor='e').pack(side='left') tk.Entry(frame_dst, textvariable=self.dst_dir, font=("Consolas", 10), width=50, state='readonly').pack(side='left', padx=5, fill='x', expand=True) # 进度条 frame_prog = tk.Frame(self.root) frame_prog.pack(fill='x', padx=25, pady=10) self.progress = ttk.Progressbar(frame_prog, length=720, mode='determinate') self.progress.pack() self.percent_label = tk.Label(frame_prog, text="0%", font=("Consolas", 14, "bold")) self.percent_label.pack(pady=3) self.status_label = tk.Label(frame_prog, text="就绪", font=("微软雅黑", 11)) self.status_label.pack(pady=2) # 统计面板 frame_stats = tk.LabelFrame(self.root, text="实时统计", font=("微软雅黑", 10), padx=10, pady=5) frame_stats.pack(fill='x', padx=25, pady=5) tk.Label(frame_stats, text="总文件:", font=("微软雅黑", 10)).grid(row=0, column=0, padx=5) tk.Label(frame_stats, textvariable=self.total_files, font=("Consolas", 11, "bold"), fg='blue').grid(row=0, column=1, padx=10) tk.Label(frame_stats, text="成功:", font=("微软雅黑", 10)).grid(row=0, column=2, padx=5) tk.Label(frame_stats, textvariable=self.ok_files, font=("Consolas", 11, "bold"), fg='green').grid(row=0, column=3, padx=10) tk.Label(frame_stats, text="失败:", font=("微软雅黑", 10)).grid(row=0, column=4, padx=5) # 失败数做成可点击的按钮样式 self.fail_btn = tk.Button(frame_stats, textvariable=self.fail_files, font=("Consolas", 11, "bold"), fg='red', bd=0, cursor="hand2", command=self.show_error_details) self.fail_btn.grid(row=0, column=5, padx=10) tk.Label(frame_stats, text="(点击查看详情)", font=("微软雅黑", 8), fg='gray').grid(row=0, column=6, padx=2) tk.Label(frame_stats, text="速度:", font=("微软雅黑", 10)).grid(row=0, column=7, padx=5) tk.Label(frame_stats, textvariable=self.speed_var, font=("Consolas", 11, "bold"), fg='orange').grid(row=0, column=8, padx=10) # 按钮 frame_btn = tk.Frame(self.root) frame_btn.pack(pady=10) self.btn_start = tk.Button(frame_btn, text="▶ 开始复制", command=self.start_copy, font=("微软雅黑", 13, "bold"), bg='#4CAF50', fg='white', width=16, height=2) self.btn_start.pack(side='left', padx=10) self.btn_stop = tk.Button(frame_btn, text="⏹ 停止", command=self.stop_copy, font=("微软雅黑", 13), bg='#f44336', fg='white', width=12, height=2, state='disabled') self.btn_stop.pack(side='left', padx=10) self.btn_errors = tk.Button(frame_btn, text="📋 错误详情", command=self.show_error_details, font=("微软雅黑", 13), bg='#ff9800', fg='white', width=12, height=2, state='disabled') self.btn_errors.pack(side='left', padx=10) # 日志区域 tk.Label(self.root, text="日志:", font=("微软雅黑", 10)).pack(anchor='w', padx=25) self.log_box = scrolledtext.ScrolledText(self.root, width=95, height=12, font=("Consolas", 9)) self.log_box.pack(padx=25, pady=5, fill='both', expand=True) def log(self, msg): self.log_box.insert('end', msg + '\n') self.log_box.see('end') def auto_detect(self): """自动检测U盘""" self.log("[扫描] 正在查找U盘...") found = None for drive in 'DEFGHIJKLMNOPQRSTUVWXYZ': path = f'{drive}:\\{ABC_FOLDER}\\{SRC_FOLDER_NAME}' if os.path.isdir(path): found = path break if found: self.src_dir.set(found) self.log(f"[发现] U盘路径: {found}") self.status_label.config(text="✓ U盘已就绪,点击开始复制", fg='green') else: self.src_dir.set("") self.log("[未找到] 未检测到包含 .gradle 的U盘") self.status_label.config(text="✗ 未检测到U盘,请插入后点击刷新", fg='red') def start_copy(self): src = self.src_dir.get() dst = self.dst_dir.get() if not src or not os.path.isdir(src): messagebox.showerror("错误", "源目录无效,请检查U盘是否插入") return # 重置状态 self.is_running = True self.error_records.clear() self.btn_start.config(state='disabled') self.btn_stop.config(state='normal') self.btn_errors.config(state='disabled') self.done_files.set(0) self.ok_files.set(0) self.fail_files.set(0) self.bytes_copied = 0 self.log_box.delete(1.0, 'end') self.log(f"[源] {src}") self.log(f"[目标] {dst}") self.log("[扫描] 正在枚举文件...") # 收集任务 tasks = [] total_size = 0 for root, dirs, files in os.walk(src): for f in files: s = os.path.join(root, f) rel = os.path.relpath(s, src) d = os.path.join(dst, rel) try: size = os.path.getsize(s) except: size = 0 tasks.append((s, d, size)) total_size += size self.total_files.set(len(tasks)) self.progress['maximum'] = len(tasks) self.progress['value'] = 0 self.percent_label.config(text="0%") self.log(f"[就绪] 共 {len(tasks)} 个文件,约 {total_size/1024/1024:.1f} MB") self.status_label.config(text="复制中...", fg='orange') # 启动工作线程 self.log(f"[线程] 启动 {THREAD_COUNT} 个复制线程") def worker(): while self.is_running: try: s, d, size = self.task_queue.get(timeout=0.3) except: continue try: os.makedirs(os.path.dirname(d), exist_ok=True) shutil.copy2(s, d) with self.lock: self.bytes_copied += size self.root.after(0, self.on_file_done, s, d, True, size, None) except Exception as e: import traceback tb = traceback.format_exc() self.root.after(0, self.on_file_done, s, d, False, 0, str(e), tb) self.task_queue.task_done() for _ in range(THREAD_COUNT): t = threading.Thread(target=worker, daemon=True) t.start() # 投递任务 for t in tasks: self.task_queue.put(t) # 速度监控 def speed_monitor(): import time last_bytes = 0 while self.is_running: time.sleep(1) with self.lock: current = self.bytes_copied diff = current - last_bytes last_bytes = current mbps = diff / 1024 / 1024 self.root.after(0, lambda m=mbps: self.speed_var.set(f"{m:.1f} MB/s")) threading.Thread(target=speed_monitor, daemon=True).start() # 监控完成 def check_done(): if self.task_queue.unfinished_tasks == 0 and self.is_running: self.is_running = False self.btn_start.config(state='normal') self.btn_stop.config(state='disabled') if self.fail_files.get() > 0: self.btn_errors.config(state='normal') self.status_label.config(text=f"✓ 完成 (有 {self.fail_files.get()} 个错误)", fg='orange') else: self.status_label.config(text="✓ 复制完成!", fg='green') self.speed_var.set("0 MB/s") self.log("\n" + "="*40) self.log(f"[完成] 成功: {self.ok_files.get()} 失败: {self.fail_files.get()}") if self.fail_files.get() > 0: self.log(f"[提示] 点击"错误详情"查看失败的文件及原因") elif self.is_running: self.root.after(100, check_done) self.root.after(100, check_done) def on_file_done(self, src, dst, success, size, error_msg, traceback_str=None): self.done_files.set(self.done_files.get() + 1) self.progress['value'] = self.done_files.get() pct = int(self.done_files.get() / max(self.total_files.get(), 1) * 100) self.percent_label.config(text=f"{pct}%") rel = os.path.relpath(src, self.src_dir.get()) if success: self.ok_files.set(self.ok_files.get() + 1) if self.ok_files.get() <= 20 or self.ok_files.get() % 50 == 0 or self.total_files.get() - self.done_files.get() < 5: self.log(f"[OK] {rel}") elif self.ok_files.get() == 21: self.log("[...] 后续文件省略显示 ...") else: self.fail_files.set(self.fail_files.get() + 1) self.error_records.append({ 'src': src, 'dst': dst, 'rel': rel, 'error': error_msg, 'traceback': traceback_str or error_msg }) self.log(f"[FAIL] {rel}: {error_msg}") def stop_copy(self): self.is_running = False self.btn_start.config(state='normal') self.btn_stop.config(state='disabled') if self.fail_files.get() > 0: self.btn_errors.config(state='normal') self.status_label.config(text="✗ 已停止", fg='red') self.speed_var.set("0 MB/s") self.log("[停止] 用户中断复制") def show_error_details(self): """弹出错误详情窗口""" if not self.error_records: messagebox.showinfo("错误详情", "没有错误记录") return win = tk.Toplevel(self.root) win.title(f"错误详情 - 共 {len(self.error_records)} 个") win.geometry("900x600") win.transient(self.root) win.grab_set() # 顶部信息 tk.Label(win, text=f"共 {len(self.error_records)} 个文件复制失败", font=("微软雅黑", 14, "bold"), fg='red').pack(pady=10) tk.Label(win, text="点击列表中的项目查看详细错误信息", font=("微软雅黑", 10), fg='gray').pack() # 左右分栏 paned = tk.PanedWindow(win, orient=tk.HORIZONTAL) paned.pack(fill='both', expand=True, padx=10, pady=10) # 左侧:文件列表 left_frame = tk.LabelFrame(paned, text="失败文件列表", font=("微软雅黑", 10)) paned.add(left_frame, width=400) listbox = tk.Listbox(left_frame, font=("Consolas", 10), selectmode=tk.SINGLE) scrollbar = tk.Scrollbar(left_frame, command=listbox.yview) listbox.config(yscrollcommand=scrollbar.set) listbox.pack(side='left', fill='both', expand=True, padx=5, pady=5) scrollbar.pack(side='right', fill='y', pady=5) for i, rec in enumerate(self.error_records): listbox.insert('end', f"{i+1}. {rec['rel']}") # 右侧:详细信息 right_frame = tk.LabelFrame(paned, text="详细信息", font=("微软雅黑", 10)) paned.add(right_frame, width=500) # 文件路径 tk.Label(right_frame, text="源文件:", font=("微软雅黑", 10, "bold")).pack(anchor='w', padx=5, pady=(5,0)) src_text = scrolledtext.ScrolledText(right_frame, height=2, font=("Consolas", 9), wrap='word') src_text.pack(fill='x', padx=5, pady=2) src_text.config(state='disabled') tk.Label(right_frame, text="目标路径:", font=("微软雅黑", 10, "bold")).pack(anchor='w', padx=5) dst_text = scrolledtext.ScrolledText(right_frame, height=2, font=("Consolas", 9), wrap='word') dst_text.pack(fill='x', padx=5, pady=2) dst_text.config(state='disabled') tk.Label(right_frame, text="错误信息:", font=("微软雅黑", 10, "bold"), fg='red').pack(anchor='w', padx=5) err_text = scrolledtext.ScrolledText(right_frame, height=4, font=("Consolas", 9), wrap='word', fg='red') err_text.pack(fill='x', padx=5, pady=2) err_text.config(state='disabled') tk.Label(right_frame, text="完整堆栈:", font=("微软雅黑", 10, "bold")).pack(anchor='w', padx=5) tb_text = scrolledtext.ScrolledText(right_frame, height=10, font=("Consolas", 9), wrap='word') tb_text.pack(fill='both', expand=True, padx=5, pady=2) tb_text.config(state='disabled') # 底部按钮 btn_frame = tk.Frame(win) btn_frame.pack(fill='x', padx=10, pady=10) def copy_all_errors(): lines = [] for rec in self.error_records: lines.append(f"文件: {rec['rel']}") lines.append(f"源: {rec['src']}") lines.append(f"目标: {rec['dst']}") lines.append(f"错误: {rec['error']}") lines.append(f"堆栈:\n{rec['traceback']}") lines.append("-" * 50) text = "\n".join(lines) self.root.clipboard_clear() self.root.clipboard_append(text) messagebox.showinfo("已复制", "所有错误信息已复制到剪贴板") tk.Button(btn_frame, text="📋 复制全部", command=copy_all_errors, font=("微软雅黑", 11), bg='#2196F3', fg='white', width=12).pack(side='left', padx=5) tk.Button(btn_frame, text="❌ 关闭", command=win.destroy, font=("微软雅黑", 11), bg='#f44336', fg='white', width=12).pack(side='right', padx=5) def on_select(event): sel = listbox.curselection() if not sel: return idx = sel[0] rec = self.error_records[idx] for widget, text in [(src_text, rec['src']), (dst_text, rec['dst']), (err_text, rec['error']), (tb_text, rec['traceback'])]: widget.config(state='normal') widget.delete(1.0, 'end') widget.insert('end', text) widget.config(state='disabled') listbox.bind('<>', on_select) # 默认选中第一个 if self.error_records: listbox.selection_set(0) on_select(None) if __name__ == '__main__': root = tk.Tk() app = GradleCopyApp(root) root.mainloop() ```