In this tutorial, weβll build a Python application that:
Lets users monitor folders.
Categorizes files by size.
Organizes files into folders automatically.
Provides a live file preview with filtering and sorting.
Weβll be using Tkinter, watchdog, threading, and tkinterdnd2 for drag-and-drop support.
Step 1: Import Required Libraries
First, we need to import the libraries for GUI, file handling, and background monitoring.
import os
import json
import shutil
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinterdnd2 import DND_FILES, TkinterDnD
from threading import Thread
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import sv_ttk
Explanation:
os, shutil β for interacting with the file system.
json β to save/load folder configurations.
tkinter, ttk β GUI elements.
tkinterdnd2 β drag-and-drop functionality.
watchdog β monitors folders in real-time.
threading β allows long-running tasks without freezing the GUI.
Step 2: Configuration & Helper Functions
Weβll store monitored folders in a JSON file and create some helper functions.
CONFIG_FILE = "folders_data.json"
def load_folders():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return []
def save_folders(folders_data):
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(folders_data, f, ensure_ascii=False, indent=4)
Explanation:
load_folders() β reads previously monitored folders from JSON.
save_folders() β writes current folders to JSON for persistence.
We also need some utility functions for file handling:
def sanitize_folder_name(name):
return "".join(c for c in name if c not in r'<>:"/\|?*')
def format_size(size_bytes):
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024**2:
return f"{size_bytes/1024:.2f} KB"
elif size_bytes < 1024**3:
return f"{size_bytes/1024**2:.2f} MB"
else:
return f"{size_bytes/1024**3:.2f} GB"
def categorize_file(size):
if size < 1024*1024:
return "Small_1MB"
elif size < 10*1024*1024:
return "Medium_1-10MB"
else:
return "Large_10MB_plus"
Explanation:
sanitize_folder_name() β removes illegal characters for folder names.
format_size() β converts bytes into human-readable format.
categorize_file() β groups files by size into small, medium, or large.
Step 3: Create the Main Application Window
We use TkinterDnD for drag-and-drop support.
root = TkinterDnD.Tk()
root.title("π File Size Organizer Pro - Filter & Sort")
root.geometry("1300x620")
Explanation:
TkinterDnD.Tk() β initializes the main window with drag-and-drop support.
title and geometry β set the window title and size.
Step 4: Global Variables
Weβll use some global variables for storing folder data, filtered files, and observer state.
folders_data = load_folders()
last_operation = []
combined_files = []
filtered_files = []
observer = None
current_filter = tk.StringVar(value="All")
current_sort = tk.StringVar(value="Name")
watcher_paused = False
Explanation:
folders_data β all folders being monitored.
combined_files β all files scanned from these folders.
filtered_files β filtered/sorted files for display.
observer β watchdog observer for real-time monitoring.
current_filter & current_sort β track GUI dropdown selections.
Step 5: Add & Remove Folders
Weβll create functions to add or remove folders.
def add_folder(path):
path = path.strip()
if not path:
messagebox.showwarning("Invalid Folder", "Please select or enter a folder path.")
return
path = os.path.abspath(path)
if not os.path.isdir(path):
messagebox.showwarning("Invalid Folder", "The selected path is not a valid folder.")
return
if path in folders_data:
messagebox.showinfo("Already Added", "This folder is already being monitored.")
return
folders_data.append(path)
save_folders(folders_data)
start_watcher(path)
refresh_combined_preview()
Explanation:
Validates the folder path.
Prevents duplicates.
Saves to JSON and starts a watcher for real-time updates.
def remove_folder(path):
path = os.path.abspath(path.strip())
if path in folders_data:
folders_data.remove(path)
save_folders(folders_data)
refresh_combined_preview()
set_status(f"Removed folder: {path}")
else:
messagebox.showwarning("Not Found", "Folder not found in the list.")
Explanation:
Removes a folder from monitoring and refreshes the preview.
Step 6: Scan Folders & Refresh Preview
def scan_folder(folder_path):
files = []
for f in os.listdir(folder_path):
path = os.path.join(folder_path, f)
if os.path.isfile(path):
try:
size = os.path.getsize(path)
except (FileNotFoundError, PermissionError):
continue
category = sanitize_folder_name(categorize_file(size))
files.append((folder_path, f, size, category))
return files
def refresh_combined_preview():
global combined_files
combined_files = []
for folder in folders_data:
combined_files.extend(scan_folder(folder))
apply_filter_sort()
Explanation:
scan_folder() β reads files in a folder and categorizes them.
refresh_combined_preview() β combines files from all folders and applies filter/sort.
Step 7: Filter & Sort Files
def apply_filter_sort():
global filtered_files
filtered_files = combined_files.copy() if current_filter.get() == "All" else [f for f in combined_files if f[3] == current_filter.get()]
sort_key = current_sort.get()
if sort_key == "Name":
filtered_files.sort(key=lambda x: x[1].lower())
elif sort_key == "Size":
filtered_files.sort(key=lambda x: x[2])
elif sort_key == "Folder":
filtered_files.sort(key=lambda x: x[0].lower())
elif sort_key == "Category":
filtered_files.sort(key=lambda x: x[3].lower())
update_file_tree()
Explanation:
Filters files based on category.
Sorts files based on Name, Size, Folder, or Category.
Step 8: Display Files in GUI
def update_file_tree():
file_tree.delete(*file_tree.get_children())
counts = {"Small_1MB":0, "Medium_1-10MB":0, "Large_10MB_plus":0}
for folder, f, size, category in filtered_files:
file_tree.insert("", "end", values=(folder, f, format_size(size), category))
counts[category] += 1
total_files_var.set(f"Total Files: {len(filtered_files)} | Small: {counts['Small_1MB']} | Medium: {counts['Medium_1-10MB']} | Large: {counts['Large_10MB_plus']}")
Explanation:
Updates the Treeview widget with current files.
Shows counts by category.
Step 9: Organize & Undo Files
def organize_files_thread():
global watcher_paused
watcher_paused = True
for folder, f, size, category in combined_files:
src = os.path.join(folder, f)
dst_folder = os.path.join(folder, category)
os.makedirs(dst_folder, exist_ok=True)
dst = os.path.join(dst_folder, f)
try:
shutil.move(src, dst)
except Exception:
pass
watcher_paused = False
refresh_combined_preview()
Explanation:
Moves files into categorized folders.
Pauses the watcher to avoid triggering real-time updates during move.
def undo_last_operation():
# Reverse the last organize operation
pass # Implement similarly to `organize_files_thread()` with reversed moves
Step 10: Real-time Folder Watching
class FolderEventHandler(FileSystemEventHandler):
def on_any_event(self, event):
if watcher_paused:
return
root.after(200, refresh_combined_preview)
def start_watcher(folder_path):
global observer
if observer is None:
observer = Observer()
observer.start()
handler = FolderEventHandler()
observer.schedule(handler, folder_path, recursive=True)
Explanation:
Uses watchdog to detect any file changes.
Updates GUI automatically.
Step 11: Drag & Drop Support
def drop(event):
paths = root.tk.splitlist(event.data)
for path in paths:
if os.path.isdir(path):
add_folder(path)
root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', drop)
Explanation:
Enables dragging folders into the app.
Automatically adds dropped folders.
Step 12: GUI Layout
Set up the GUI frames, buttons, Treeview, filters, progress bar, and status bar.
Due to length, this can be broken into smaller sub-steps in your final tutorial.

Top comments (0)