From 322cf7335cda053fb37596fa9312eb0ce5bca773 Mon Sep 17 00:00:00 2001 From: ahdoawhfo Date: Sun, 23 Jun 2024 23:09:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E7=9B=AE=E5=BD=95=E6=89=AB=E6=8F=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=BA=86=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpaceSniffer.py | 230 ++++++++++++++++++------------------------------ 1 file changed, 88 insertions(+), 142 deletions(-) diff --git a/SpaceSniffer.py b/SpaceSniffer.py index f6633ec..15480ab 100644 --- a/SpaceSniffer.py +++ b/SpaceSniffer.py @@ -1,100 +1,74 @@ import os import threading import tkinter as tk -from tkinter import ttk, messagebox -import tkinter.filedialog +from tkinter import ttk, filedialog, messagebox from queue import Queue +from send2trash import send2trash import subprocess -import send2trash class FolderScanner(threading.Thread): - def __init__(self, start_path, queue): - """ - 初始化函数,创建一个线程并设置起始路径和队列。 - - Args: - start_path (str): 起始路径,表示要遍历的文件夹的根目录。 - queue (Queue): 队列对象,用于线程间的通信,存放待处理或已处理过的文件或文件夹路径。 - - Returns: - None - """ - # 使用super()调用父类threading.Thread的__init__方法 - super().__init__() + def __init__(self, start_path, queue, progress_queue): + threading.Thread.__init__(self) self.start_path = start_path self.queue = queue + self.progress_queue = progress_queue + self.total_items = 0 def run(self): - """ - 运行扫描文件夹并获取文件信息的操作。 - - Args: - 无参数。 - - Returns: - 无返回值。 - - """ + self.total_items = sum( + [len(files) + len(dirs) for r, dirs, files in os.walk(self.start_path)] + ) self.scan_folder(self.start_path) + self.queue.put(None) # Indicate that scanning is done def scan_folder(self, folder): - """ - 遍历指定文件夹下的所有子文件夹和文件,并将它们的路径、大小、父文件夹路径和类型(文件夹或文件)放入队列中。 - - Args: - folder (str): 需要遍历的文件夹路径。 - - Returns: - None. - - """ + scanned_items = 0 for dirpath, dirnames, filenames in os.walk(folder): for dirname in dirnames: folder_path = os.path.join(dirpath, dirname) size = self.get_size(folder_path) self.queue.put((folder_path, size, dirpath, "folder")) + scanned_items += 1 + self.update_progress(scanned_items, folder_path) + for filename in filenames: file_path = os.path.join(dirpath, filename) size = os.path.getsize(file_path) self.queue.put((file_path, size, dirpath, "file")) + scanned_items += 1 + self.update_progress(scanned_items, file_path) def get_size(self, path): - # 初始化总大小为0 total_size = 0 - # 遍历指定路径下的所有文件和目录 for dirpath, dirnames, filenames in os.walk(path): - # 遍历当前目录下的所有文件 for f in filenames: - # 拼接文件的完整路径 fp = os.path.join(dirpath, f) - # 将文件大小累加到总大小中 total_size += os.path.getsize(fp) return total_size + def update_progress(self, scanned_items, current_path): + progress = (scanned_items / self.total_items) * 100 + self.progress_queue.put( + (progress, scanned_items, self.total_items, current_path) + ) + class App(tk.Tk): def __init__(self, start_path): - """ - 初始化目录扫描器窗口 - - Args: - start_path (str): 开始扫描的目录路径 - - Returns: - None - """ super().__init__() + self.withdraw() self.title("目录扫描器 V1.0") self.start_path = start_path self.queue = Queue() - self.scanner = FolderScanner(start_path, self.queue) + self.progress_queue = Queue() + self.progress_window = ProgressWindow(self) + + self.scanner = FolderScanner(start_path, self.queue, self.progress_queue) self.scanner.start() self.style = ttk.Style(self) - self.style.configure( - "Treeview", font=("Helvetica", 10), rowheight=25 - ) # 设置行高 + self.style.configure("Treeview", font=("Helvetica", 10), rowheight=25) self.style.configure("Treeview.Heading", font=("Helvetica", 12, "bold")) self.tree = ttk.Treeview(self, style="Treeview") @@ -134,21 +108,12 @@ class App(tk.Tk): self.populate_root() self.update_tree() + self.update_progress() self.protocol("WM_DELETE_WINDOW", self.on_close) self.set_window_size() def populate_root(self): - """ - 在树形控件中填充根节点。 - - Args: - 无参数。 - - Returns: - 无返回值。 - - """ size = self.scanner.get_size(self.start_path) / 1024 / 1024 self.tree.insert( "", @@ -160,19 +125,15 @@ class App(tk.Tk): ) def update_tree(self): - """ - 更新树形结构,将磁盘文件信息更新到树形视图中。 - - Args: - 无 - - Returns: - 无 - - """ if not self.is_sorting: while not self.queue.empty(): - path, size, parent, tag = self.queue.get() + item = self.queue.get() + if item is None: # Scanning is done + self.progress_window.destroy() + self.deiconify() + return + + path, size, parent, tag = item size_mb = size / 1024 / 1024 parent_iid = self.get_iid(parent) if parent == self.start_path: @@ -188,29 +149,26 @@ class App(tk.Tk): ) self.after(100, self.update_tree) + def update_progress(self): + if not self.progress_window.winfo_exists(): # Check if the window still exists + return + while not self.progress_queue.empty(): + progress, scanned_items, total_items, current_path = ( + self.progress_queue.get() + ) + self.progress_window.progress_var.set(progress) + self.progress_window.progress_bar["value"] = progress + self.progress_window.progress_label.config( + text=f"扫描进度: {scanned_items} / {total_items}" + ) + self.progress_window.path_label.config(text=f"正在扫描: {current_path}") + + self.after(100, self.update_progress) + def get_iid(self, path): - """ - 获取指定路径的iid值 - - Args: - path (str): 文件或目录的路径 - - Returns: - str: 路径本身作为iid值返回 - """ return path def sort_by_size(self): - """ - 根据文件或文件夹的大小对文件列表进行排序。 - - Args: - 无参数。 - - Returns: - 无返回值,直接修改当前实例的文件列表。 - - """ self.sort_items( lambda x: ( 0 if x[1] == "folder" and self.folder_first_var.get() else 1, @@ -225,16 +183,6 @@ class App(tk.Tk): self.size_sort_order = not self.size_sort_order def sort_by_name(self): - """ - 根据名称对列表进行排序 - - Args: - 无 - - Returns: - 无返回值,但会改变对象的内部状态 - - """ self.sort_items( lambda x: ( 0 if x[1] == "folder" and self.folder_first_var.get() else 1, @@ -245,17 +193,6 @@ class App(tk.Tk): self.name_sort_order = not self.name_sort_order def sort_items(self, key, reverse): - """ - 对树形控件中的指定目录进行排序。 - - Args: - key (callable): 用于排序的key函数。 - reverse (bool): 是否降序排序。 - - Returns: - None - - """ if self.folder_first_var.get(): selected = self.tree.selection() if not selected: @@ -316,16 +253,6 @@ class App(tk.Tk): self.tree.move(item[2], parent, index) def show_context_menu(self, event): - """ - 显示上下文菜单 - - Args: - event (tkinter.Event): 鼠标事件对象 - - Returns: - None - - """ item = self.tree.identify_row(event.y) if item: self.tree.selection_set(item) @@ -363,13 +290,11 @@ class App(tk.Tk): def delete_item(self, item): path = self.tree.item(item, "text").split(" ")[0] - full_path = os.path.normpath( - os.path.join(self.start_path, path) - ) # 使用os.path.normpath来规范化路径 + full_path = os.path.normpath(os.path.join(self.start_path, path)) confirm = messagebox.askyesno("提示", f"你确定要删除 {full_path} 吗?") if confirm: try: - send2trash.send2trash(full_path) + send2trash(full_path) self.tree.delete(item) except Exception as e: messagebox.showerror("错误", f"无法删除 {full_path}\n{e}") @@ -379,12 +304,18 @@ class App(tk.Tk): for item in self.tree.get_children(): self.tree.delete(item) self.populate_root() - self.scanner = FolderScanner(self.start_path, self.queue) + self.scanner = FolderScanner(self.start_path, self.queue, self.progress_queue) + self.progress_window = ProgressWindow(self) # Create a new progress window + self.withdraw() # Hide the main window self.scanner.start() + self.update_tree() # Start updating the tree again + self.update_progress() # Start updating the progress again def on_close(self): - self.scanner.join() + if self.scanner.is_alive(): + self.scanner.join() self.destroy() + root.destroy() def set_window_size(self): screen_width = self.winfo_screenwidth() @@ -409,23 +340,38 @@ class App(tk.Tk): font=(current_font.split()[0], new_font_size + 2, "bold"), ) - def sort_root_items(self, by="size"): - if by == "size": - self.sort_by_size() - elif by == "name": - self.sort_by_name() - def update_folder_first_var(self): - if self.folder_first_var.get(): - self.folder_first_var.set(False) - else: - self.folder_first_var.set(True) + self.folder_first_var.set(not self.folder_first_var.get()) + + +class ProgressWindow(tk.Toplevel): + def __init__(self, parent): + super().__init__(parent) + self.title("扫描进度") + self.geometry("500x200") + self.label = tk.Label( + self, text="扫描中,请稍等", fg="deepskyblue", font=("Helvetica", 16) + ) + self.label.pack(pady=20) + self.progress_var = tk.DoubleVar() + self.progress_bar = ttk.Progressbar( + self, variable=self.progress_var, maximum=100 + ) + self.progress_bar.pack(fill=tk.X, padx=20, pady=10) + self.progress_label = tk.Label(self, text="扫描进度: 0 / 0") + self.progress_label.pack(pady=5) + self.path_label = tk.Label(self, text="正在扫描: ") + self.path_label.pack(pady=5) + self.protocol("WM_DELETE_WINDOW", self.on_close) + + def on_close(self): + pass # Prevent the user from closing the progress window if __name__ == "__main__": root = tk.Tk() root.withdraw() - start_path = tkinter.filedialog.askdirectory() + start_path = filedialog.askdirectory() if start_path: app = App(start_path) app.mainloop()