DEV Community

Mate Technologies
Mate Technologies

Posted on

Step-by-Step Guide: Building FileOMate – A Smart File Organizer in Python

In this tutorial, we’ll build FileOMate, a smart file organizer that automatically sorts your files in real time. It includes a GUI, drag-and-drop support, customizable rules, preview mode, and real-time monitoring using watchdog.

We’ll go step by step, breaking down the code into small, understandable chunks.

Step 1: Setting Up Dependencies

FileOMate relies on a few Python packages:

pip install ttkbootstrap watchdog tkinterdnd2
Enter fullscreen mode Exit fullscreen mode

Explanation:

ttkbootstrap → Modern themes for Tkinter GUI.

watchdog → Real-time file system monitoring.

tkinterdnd2 → Drag-and-drop folder support (optional).

Step 2: Import Required Modules

We start by importing the standard library modules and installed packages:

import sys
import time
import json
import queue
import shutil
import threading
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox

import ttkbootstrap as tb
from ttkbootstrap.constants import *
Enter fullscreen mode Exit fullscreen mode

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

Optional drag & drop support

try:
    from tkinterdnd2 import TkinterDnD, DND_FILES
    DND = True
except ImportError:
    DND = False
Enter fullscreen mode Exit fullscreen mode

Explanation:

queue → Thread-safe communication between background threads and GUI.

shutil → For moving files safely.

watchdog → Observes file changes in real time.

TkinterDnD → Enables drag-and-drop folder functionality.

Step 3: Create a Watchdog Handler

FolderHandler responds when a new file is created in a watched folder.

class FolderHandler(FileSystemEventHandler):
    def __init__(self, app):
        self.app = app

    def on_created(self, event):
        if not event.is_directory:
            self.app.handle_new_file(Path(event.src_path))
Enter fullscreen mode Exit fullscreen mode

Explanation:

on_created is called whenever a file is added.

self.app.handle_new_file processes the new file safely.

Step 4: Building the Main App Class

The FileOMateApp class handles GUI, file rules, and real-time monitoring.

class FileOMateApp:
    APP_NAME = "FileOMate"
    APP_VERSION = "1.2.0"

    def __init__(self):
        self.root = TkinterDnD.Tk() if DND else tk.Tk()
        tb.Style(theme="darkly")
        self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
        self.root.geometry("1180x680")
Enter fullscreen mode Exit fullscreen mode

Explanation:

Supports drag-and-drop if TkinterDnD is installed.

Uses a modern dark theme with ttkbootstrap.

Sets window size and title.

Step 5: Adding State Variables

These variables track the app’s state, counters, and options.

        self.watch_folders = []
        self.unmatched_folder = None
        self.running = False
        self.preview_mode = True

        self.ui_queue = queue.Queue()

        self.moved = tk.IntVar(value=0)
        self.unmatched = tk.IntVar(value=0)
        self.errors = tk.IntVar(value=0)

        self.preview_moved = 0
        self.preview_unmatched = 0

        self.auto_unmatched_var = tk.BooleanVar(value=True)
        self.recursive_var = tk.BooleanVar(value=True)
        self.status_var = tk.StringVar(value="Preview Mode")
Enter fullscreen mode Exit fullscreen mode

Explanation:

watch_folders → List of folders to monitor.

ui_queue → Thread-safe messages to display in the GUI log.

IntVar and BooleanVar → Tkinter variables that automatically update the UI.

Step 6: Safe File Move Utility

A helper function that moves files safely, avoiding overwrites.

def safe_move(self, src: Path, dst: Path) -> Path | None:
    try:
        dst.parent.mkdir(parents=True, exist_ok=True)
        base, ext = dst.stem, dst.suffix
        final_dst = dst
        i = 1
        while final_dst.exists():
            final_dst = dst.with_name(f"{base} ({i}){ext}")
            i += 1
        shutil.move(str(src), str(final_dst))
        return final_dst
    except Exception as e:
        self.ui_queue.put(f"❌ Failed to move {src.name}: {e}\n")
        if not self.preview_mode:
            self.errors.set(self.errors.get() + 1)
        return None
Enter fullscreen mode Exit fullscreen mode

Explanation:

Creates missing folders automatically.

Automatically renames duplicates like file (1).txt.

Reports errors safely to the UI log.

Step 7: Matching Files to Rules

We need a way to decide where each file goes:

def match_rule(self, file: Path):
    for r in self.rule_table.get_children():
        keyword, target_folder = self.rule_table.item(r)["values"]
        if keyword in file.name.lower():
            return Path(target_folder)
    return None
Enter fullscreen mode Exit fullscreen mode

Explanation:

Each rule has a keyword and a target folder.

If a file name contains a keyword, it is moved to the target folder.

Returns None if no match is found.

Step 8: Handling a New File

def handle_new_file(self, file_path: Path):
    f = Path(file_path).resolve()
    if not f.is_file():
        return

    target_folder = self.match_rule(f)
    if not target_folder:
        if self.auto_unmatched_var.get() and self.unmatched_folder:
            target_folder = self.unmatched_folder
        else:
            if not self.preview_mode:
                self.unmatched.set(self.unmatched.get() + 1)
            self.ui_queue.put(f"⚠ Skipped (no rule): {f.name}\n")
            return

    if self.preview_mode:
        self.ui_queue.put(f"{f.name} → {target_folder}\n")
        return

    final_dst = self.safe_move(f, target_folder / f.name)
    if final_dst:
        self.ui_queue.put(f"✔ Moved: {f.name} → {final_dst}\n")
        self.moved.set(self.moved.get() + 1)
Enter fullscreen mode Exit fullscreen mode

Explanation:

Checks if file matches a rule or goes to the "unmatched" folder.

Logs actions in preview mode without moving files.

Uses safe_move for real moves and updates counters.

Step 9: Preview vs Real Moves

def preview_scan(self):
    self.preview_mode = True
    self.status_var.set("Preview Mode")
    self.ui_queue.put("--- Preview ---\n")
    for folder in self.watch_folders:
        for f in Path(folder).rglob("*") if self.recursive_var.get() else Path(folder).iterdir():
            if f.is_file():
                target_folder = self.match_rule(f)
                if target_folder:
                    self.ui_queue.put(f"{f.name} → {target_folder}\n")
                else:
                    self.ui_queue.put(f"{f.name} → unmatched\n")
Enter fullscreen mode Exit fullscreen mode

Explanation:

Shows where files would be moved without actually moving them.

Helps users confirm rules before running.

Step 10: Running the Observer

def start(self):
    self.running = True
    self.preview_mode = False
    self.status_var.set("Running")
    self.observer = Observer()
    handler = FolderHandler(self)
    for folder in self.watch_folders:
        self.observer.schedule(handler, str(folder), recursive=self.recursive_var.get())
    self.observer.start()
Enter fullscreen mode Exit fullscreen mode

Explanation:

Observer monitors the file system in real-time.

Calls FolderHandler whenever a file is added.

Runs in the background while the GUI remains responsive.

Step 11: Building the GUI

We use ttkbootstrap for modern styling. Example: Watch folders panel.

lf = tb.Labelframe(self.root, text="Watch Folders", padding=10)
lf.pack(fill="x", padx=10, pady=5)

self.folder_list = tk.Listbox(lf, height=4)
self.folder_list.pack(side="left", fill="x", expand=True)

tb.Button(lf, text="Add Folder", command=self.add_watch_folder).pack(side="left", padx=6)
Enter fullscreen mode Exit fullscreen mode

Explanation:

Labelframe → Group UI elements.

Listbox → Displays folders being monitored.

Button → Allows adding new folders.

Step 12: Running the App

Finally, we run the main loop:

if __name__ == "__main__":
    FileOMateApp().run()
Enter fullscreen mode Exit fullscreen mode

Explanation:

root.mainloop() keeps the GUI running.

All threads (file processing, watchdog, UI queue) run in the background.

✅ At this point, you have a working FileOMate app that can:

Monitor folders in real time.

Organize files by rules.

Preview changes.

Safely move files with duplicate handling.

Clone full repository here: https://github.com/rogers-cyber/FileOMate

Top comments (0)