DEV Community

Mate Technologies
Mate Technologies

Posted on

πŸ“‚ Build a File Size Organizer GUI in Python with Tkinter

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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']}")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

File Size Organizer Pro - Filter & Sort

Top comments (0)