diff --git a/SpaceSniffer.py b/SpaceSniffer.py index 15480ab..1793c5a 100644 --- a/SpaceSniffer.py +++ b/SpaceSniffer.py @@ -10,36 +10,36 @@ import subprocess class FolderScanner(threading.Thread): 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 + self.start_path = start_path # 扫描路径 + self.queue = queue # 用于存储扫描结果 + self.progress_queue = progress_queue # 用于存储扫描进度 + self.total_items = 0 # 总共扫描的文件/目录数量 - def run(self): + def run(self): # 扫描文件/目录 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 + ) # 计算总共扫描的文件/目录数量 + self.scan_folder(self.start_path) # 扫描文件/目录 + self.queue.put(None) # 扫描结束 - def scan_folder(self, folder): - scanned_items = 0 - for dirpath, dirnames, filenames in os.walk(folder): - for dirname in dirnames: + def scan_folder(self, folder): # 扫描文件/目录 + 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: + 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): + def get_size(self, path): # 获取文件/目录大小 total_size = 0 for dirpath, dirnames, filenames in os.walk(path): for f in filenames: @@ -47,7 +47,7 @@ class FolderScanner(threading.Thread): total_size += os.path.getsize(fp) return total_size - def update_progress(self, scanned_items, current_path): + 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) @@ -55,7 +55,7 @@ class FolderScanner(threading.Thread): class App(tk.Tk): - def __init__(self, start_path): + def __init__(self, start_path): # 初始化 super().__init__() self.withdraw() self.title("目录扫描器 V1.0") @@ -79,12 +79,13 @@ class App(tk.Tk): self.folder_first_var = tk.BooleanVar() self.folder_first_checkbutton = tk.Checkbutton( - self, text="目录优先显示", variable=self.folder_first_var + self, + text="目录优先显示", + variable=self.folder_first_var, + command=self.update_folder_first_var, ) self.folder_first_checkbutton.pack(side=tk.LEFT, fill=tk.X) - self.folder_first_checkbutton.config(command=self.update_folder_first_var) - self.sort_by_size_button = tk.Button( self, text="根据目录/文件大小排序", command=self.sort_by_size ) @@ -100,6 +101,13 @@ class App(tk.Tk): ) self.refresh_button.pack(side=tk.LEFT, fill=tk.X) + self.reselect_button = tk.Button( + self, text="重新选择目录", command=self.reselect_directory + ) + self.reselect_button.pack(side=tk.LEFT, fill=tk.X) + + self.tree.bind("", self.on_double_click) + self.tree.bind("", self.on_enter_press) self.tree.bind("", self.show_context_menu) self.size_sort_order = True @@ -113,18 +121,47 @@ class App(tk.Tk): self.protocol("WM_DELETE_WINDOW", self.on_close) self.set_window_size() - def populate_root(self): - size = self.scanner.get_size(self.start_path) / 1024 / 1024 + def on_double_click(self, event): # V1.2 Update:当双击的是文件时,打开文件 + item = self.tree.identify_row(event.y) + if item: + if os.path.isfile(item): + self.open_item(item) + + def on_enter_press(self, event): # V1.2 Update:当回车的是文件时,打开文件 + item = self.tree.selection()[0] + if os.path.isfile(item): + self.open_item(item) + + def reselect_directory(self): # V1.2 Update:重新选择目录 + new_path = filedialog.askdirectory() + if new_path: + self.start_path = new_path + self.refresh_tree() + else: + messagebox.showinfo("提示", "没有选中任何目录,程序即将退出") + self.on_close() + + def format_size(self, size): # 格式化文件/目录大小 + units = ["B", "KB", "MB", "GB", "TB"] + index = 0 + while size >= 1024 and index < len(units) - 1: + size /= 1024.0 + index += 1 + return f"{size:.2f} {units[index]}" + + def populate_root(self): # 初始化根目录 + size = self.scanner.get_size(self.start_path) + formatted_size = self.format_size(size) self.tree.insert( "", "end", iid=self.start_path, - text=f"{os.path.basename(self.start_path)} ({size:.2f} MB)", + text=f"{os.path.basename(self.start_path)} ({formatted_size})", tags=("folder",), open=True, ) - def update_tree(self): + def update_tree(self): # 更新树状图 if not self.is_sorting: while not self.queue.empty(): item = self.queue.get() @@ -134,7 +171,7 @@ class App(tk.Tk): return path, size, parent, tag = item - size_mb = size / 1024 / 1024 + formatted_size = self.format_size(size) parent_iid = self.get_iid(parent) if parent == self.start_path: parent_iid = self.start_path @@ -143,13 +180,13 @@ class App(tk.Tk): parent_iid, "end", iid=self.get_iid(path), - text=f"{os.path.basename(path)} ({size_mb:.2f} MB)", + text=f"{os.path.basename(path)} ({formatted_size})", tags=(tag,), open=False, ) self.after(100, self.update_tree) - def update_progress(self): + def update_progress(self): # 更新扫描进度 if not self.progress_window.winfo_exists(): # Check if the window still exists return while not self.progress_queue.empty(): @@ -165,10 +202,10 @@ class App(tk.Tk): self.after(100, self.update_progress) - def get_iid(self, path): + def get_iid(self, path): # 获取目录/文件的 iid return path - def sort_by_size(self): + def sort_by_size(self): # 根据目录/文件大小排序 self.sort_items( lambda x: ( 0 if x[1] == "folder" and self.folder_first_var.get() else 1, @@ -182,7 +219,7 @@ class App(tk.Tk): ) self.size_sort_order = not self.size_sort_order - def sort_by_name(self): + def sort_by_name(self): # 根据目录/文件名排序 self.sort_items( lambda x: ( 0 if x[1] == "folder" and self.folder_first_var.get() else 1, @@ -192,7 +229,7 @@ class App(tk.Tk): ) self.name_sort_order = not self.name_sort_order - def sort_items(self, key, reverse): + def sort_items(self, key, reverse): # 排序 if self.folder_first_var.get(): selected = self.tree.selection() if not selected: @@ -252,14 +289,17 @@ class App(tk.Tk): for index, item in enumerate(sorted_items): self.tree.move(item[2], parent, index) - def show_context_menu(self, event): + def show_context_menu(self, event): # 显示右键菜单 item = self.tree.identify_row(event.y) if item: self.tree.selection_set(item) menu = tk.Menu(self, tearoff=0) - menu.add_command(label="打开文件", command=lambda: self.open_item(item)) menu.add_command( - label="打开所在目录", command=lambda: self.open_directory(item) + label="打开文件", command=lambda: self.open_item(item, "Open File") + ) + menu.add_command( + label="打开所在目录", + command=lambda: self.open_directory(item, "Open Containing Folder"), ) menu.add_command( label="删除文件/目录(放入回收站)", @@ -267,28 +307,32 @@ class App(tk.Tk): ) menu.post(event.x_root, event.y_root) - def open_item(self, item): - path = self.tree.item(item, "text").split(" ")[0] - full_path = os.path.join(self.start_path, path) + def open_item(self, item, context_menu_action=None): # 打开文件/目录 + name = self.tree.item(item, "text").split(" ")[0] try: - if os.path.isdir(full_path): - subprocess.Popen(f'explorer "{os.path.realpath(full_path)}"') + if os.path.isdir(item): + if context_menu_action == "Open File": + subprocess.Popen(f'explorer "{os.path.realpath(item)}"') + elif context_menu_action == "Open Containing Folder": + parent_folder = os.path.dirname(item) + subprocess.Popen(f'explorer "{os.path.realpath(parent_folder)}"') else: - os.startfile(full_path) + os.startfile(item) except Exception as e: - messagebox.showerror("错误", f"无法打开 {full_path}\n{e}") + messagebox.showerror("错误", f"无法打开 {name}\n{e}") - def open_directory(self, item): - path = self.tree.item(item, "text").split(" ")[0] - full_path = os.path.join(self.start_path, path) - if not os.path.isdir(full_path): - full_path = os.path.dirname(full_path) + def open_directory(self, item, context_menu_action=None): # 打开所在目录 + name = self.tree.item(item, "text").split(" ")[0] + if item == self.start_path: + parent_path = item + else: + parent_path = os.path.dirname(item) try: - subprocess.Popen(f'explorer "{os.path.realpath(full_path)}"') + subprocess.Popen(f'explorer "{os.path.realpath(parent_path)}"') except Exception as e: - messagebox.showerror("错误", f"无法打开 {full_path}\n{e}") + messagebox.showerror("错误", f"无法打开 {name}\n{e}") - def delete_item(self, item): + 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)) confirm = messagebox.askyesno("提示", f"你确定要删除 {full_path} 吗?") @@ -300,24 +344,24 @@ class App(tk.Tk): messagebox.showerror("错误", f"无法删除 {full_path}\n{e}") self.refresh_tree() - def refresh_tree(self): - for item in self.tree.get_children(): - self.tree.delete(item) + def refresh_tree(self): # 刷新树状图 + self.is_sorting = False # Reset sorting flag + self.queue.queue.clear() + self.progress_queue.queue.clear() + self.tree.delete(*self.tree.get_children()) self.populate_root() 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 + self.update_tree() + self.update_progress() - def on_close(self): + def on_close(self): # 关闭程序 if self.scanner.is_alive(): self.scanner.join() self.destroy() root.destroy() - def set_window_size(self): + def set_window_size(self): # 设置窗口大小 screen_width = self.winfo_screenwidth() screen_height = self.winfo_screenheight() window_width = int(screen_width * 0.6) @@ -327,7 +371,7 @@ class App(tk.Tk): ) self.update_font_size() - def update_font_size(self): + def update_font_size(self): # 更新字体大小 current_font = self.style.lookup("Treeview", "font") font_size = int(current_font.split()[1]) screen_width = self.winfo_screenwidth() @@ -340,12 +384,12 @@ class App(tk.Tk): font=(current_font.split()[0], new_font_size + 2, "bold"), ) - def update_folder_first_var(self): + def update_folder_first_var(self): # 更新目录优先显示变量 self.folder_first_var.set(not self.folder_first_var.get()) -class ProgressWindow(tk.Toplevel): - def __init__(self, parent): +class ProgressWindow(tk.Toplevel): # 进度窗口 + def __init__(self, parent): # 初始化 super().__init__(parent) self.title("扫描进度") self.geometry("500x200") @@ -365,7 +409,7 @@ class ProgressWindow(tk.Toplevel): self.protocol("WM_DELETE_WINDOW", self.on_close) def on_close(self): - pass # Prevent the user from closing the progress window + pass # 禁止关闭进度窗口 if __name__ == "__main__":