This is not finding the .dfm models in the directory and is saying no models found  fix the for this  import os
import re
import json
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import time
import zlib

# Placeholder for resolve_relative_path
def resolve_relative_path(relative_path):
    return relative_path

class DFMModelManager:
    def __init__(self, root):
        self.root = root
        self.root.title("DFM Models Manager")
        self.models = {}
        self.dfmpath = ""
        self.backup_filename = "rename_backup.json"
        self.create_widgets()
        self.setup_grid_weights()

    def create_widgets(self):
        # Frame for path selection
        path_frame = ttk.Frame(self.root, padding="10")
        path_frame.grid(row=0, column=0, sticky="ew")

        self.path_var = tk.StringVar()
        path_label = ttk.Label(path_frame, text="DFM Models Path:")
        path_label.pack(side=tk.LEFT, padx=(0, 5))

        path_entry = ttk.Entry(path_frame, textvariable=self.path_var)
        path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))

        browse_button = ttk.Button(path_frame, text="Browse", command=self.browse_path)
        browse_button.pack(side=tk.LEFT, padx=(0, 5))

        load_button = ttk.Button(path_frame, text="Load Models", command=self.load_models)
        load_button.pack(side=tk.LEFT, padx=(0, 5))

        # Add Generate Checksums button
        checksum_button = ttk.Button(path_frame, text="Generate Checksums", command=self.handle_generate_checksums)
        checksum_button.pack(side=tk.LEFT, padx=(0, 5))

        # Notebook for tabs
        notebook = ttk.Notebook(self.root)
        notebook.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)

        # Completed Models Tab
        self.completed_frame = ttk.Frame(notebook)
        notebook.add(self.completed_frame, text="Completed Models")
        self.create_treeview(self.completed_frame, is_completed=True)

        # Pending Models Tab
        self.pending_frame = ttk.Frame(notebook)
        notebook.add(self.pending_frame, text="Pending Models")
        self.create_treeview(self.pending_frame, is_completed=False)

        # Rename Files Tab
        self.rename_frame = ttk.Frame(notebook)
        notebook.add(self.rename_frame, text="Rename Files")
        self.create_rename_tab(self.rename_frame)

        # Frame for updating resolution
        update_frame = ttk.Frame(self.root, padding="10")
        update_frame.grid(row=2, column=0, sticky="ew")

        size_label = ttk.Label(update_frame, text="Update Size (width, height):")
        size_label.pack(side=tk.LEFT, padx=(0, 5))

        self.size_entry = ttk.Entry(update_frame)
        self.size_entry.pack(side=tk.LEFT, padx=(0, 5))
        self.size_entry.insert(0, "e.g., 256, 256")  # Placeholder text
        self.size_entry.bind("<FocusIn>", self.clear_placeholder)

        update_button = ttk.Button(update_frame, text="Update Selected", command=self.update_selected)
        update_button.pack(side=tk.LEFT, padx=(5, 0))

        # Frame for save button
        save_frame = ttk.Frame(self.root, padding="10")
        save_frame.grid(row=3, column=0, sticky="ew")

        save_button = ttk.Button(save_frame, text="Save Changes", command=self.save_changes)
        save_button.pack(side=tk.RIGHT)

    def clear_placeholder(self, event):
        if self.size_entry.get() == "e.g., 256, 256":
            self.size_entry.delete(0, tk.END)

    def setup_grid_weights(self):
        # Configure grid weights for responsiveness
        self.root.rowconfigure(1, weight=1)  # Notebook row
        self.root.columnconfigure(0, weight=1)  # Single column

    def create_treeview(self, parent, is_completed=True):
        columns = ("Model Name", "Hash URL", "Source URL", "Template", "Size")
        tree = ttk.Treeview(parent, columns=columns, show='headings', selectmode="extended")
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, anchor='center', width=200)
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=tree.yview)
        tree.configure(yscroll=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        if is_completed:
            self.tree_completed = tree
        else:
            self.tree_pending = tree

    def create_rename_tab(self, parent):
        # Frame for renaming controls
        controls_frame = ttk.LabelFrame(parent, text="Rename .dfm Files (Spaces to Underscores)", padding="10")
        controls_frame.pack(fill="x", padx=10, pady=10)

        # Buttons for preview, rename, and revert
        preview_button = ttk.Button(controls_frame, text="Preview Changes", command=self.preview_changes)
        preview_button.pack(side="left", padx=5, pady=5)

        rename_button = ttk.Button(controls_frame, text="Rename Files", command=self.rename_files)
        rename_button.pack(side="left", padx=5, pady=5)

        revert_button = ttk.Button(controls_frame, text="Revert Rename", command=self.revert_changes)
        revert_button.pack(side="left", padx=5, pady=5)

        # Treeview for displaying file changes
        tree_frame = ttk.Frame(parent)
        tree_frame.pack(fill="both", expand=True, padx=10, pady=10)

        columns = ("Original Filename", "New Filename")
        self.rename_tree = ttk.Treeview(tree_frame, columns=columns, show="headings")
        self.rename_tree.heading("Original Filename", text="Original Filename")
        self.rename_tree.heading("New Filename", text="New Filename")
        self.rename_tree.column("Original Filename", width=300)
        self.rename_tree.column("New Filename", width=300)
        self.rename_tree.pack(side="left", fill="both", expand=True)

        scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.rename_tree.yview)
        self.rename_tree.configure(yscroll=scrollbar.set)
        scrollbar.pack(side="right", fill="y")

    def browse_path(self):
        directory = filedialog.askdirectory()
        if directory:
            self.path_var.set(directory)

    def load_models(self):
        self.dfmpath = self.path_var.get()
        print(f"Loading models from: {self.dfmpath}")  # Debug print
        if not os.path.isdir(self.dfmpath):
            messagebox.showerror("Error", "Invalid directory path.")
            return

        # Clear existing models
        self.models.clear()
        for item in self.tree_completed.get_children():
            self.tree_completed.delete(item)
        for item in self.tree_pending.get_children():
            self.tree_pending.delete(item)

        # Define possible resolutions
        # Resolutions are any 3 or 4-digit numbers not followed by 'k' or 'K'
        resolution_pattern = re.compile(r'(\d{3,4})(?![kK])')

        # Walk through the directory to find .dfm files
        for root_dir, dirs, files in os.walk(self.dfmpath):
            for file in files:
                if file.lower().endswith(".dfm"):
                    full_path = os.path.join(root_dir, file)
                    relative_path = os.path.relpath(full_path, self.dfmpath)
                    # Replace backslashes with forward slashes for consistency
                    relative_path = relative_path.replace("\\", "/")
                    model_name = os.path.splitext(relative_path)[0]  # Removes .dfm extension

                    dfm_path = full_path
                    hash_file = model_name + ".hash"
                    hash_path = os.path.join(self.dfmpath, hash_file).replace("\\", "/")  # Ensure correct path

                    if os.path.exists(hash_path):
                        # Attempt to detect resolution(s) from model name
                        matches = resolution_pattern.findall(model_name)
                        # Take the first detected resolution
                        if matches:
                            res_value = int(matches[0])
                            size = (res_value, res_value)  # Ensuring tuple format
                            completed = True
                        else:
                            size = (224, 224)  # Default size as tuple
                            completed = False

                        model_key = model_name  # Includes any subdirectories like 'iperov/jackie_chan_224'

                        model_data = {
                            "hashes": {
                                "deep_swapper": {
                                    "url": None,  # Initialize as None
                                    "path": hash_path
                                }
                            },
                            "sources": {
                                "deep_swapper": {
                                    "url": None,  # Initialize as None
                                    "path": dfm_path.replace("\\", "/")
                                }
                            },
                            "template": "arcface_128_v2",
                            "size": size,  # Tuple format
                            "completed": completed
                        }

                        self.models[model_key] = model_data

                        # Insert into appropriate treeview
                        tree = self.tree_completed if completed else self.tree_pending
                        tree.insert("", "end", iid=model_key, values=(
                            model_key,
                            model_data["hashes"]["deep_swapper"]["url"],
                            model_data["sources"]["deep_swapper"]["url"],
                            model_data["template"],
                            self.format_size(model_data["size"])  # Displaying as "width x height"
                        ))

        if not self.models:
            messagebox.showinfo("Info", "No models found in the specified directory.")
        else:
            messagebox.showinfo("Success", f"Loaded {len(self.models)} models successfully.")

    def format_size(self, size_tuple):
        return f"{size_tuple[0]} x {size_tuple[1]}"

    def update_selected(self):
        # Determine which tab is currently active
        current_tab = self.get_current_tab()
        tree = self.tree_completed if current_tab == "Completed Models" else self.tree_pending

        selected = tree.selection()
        if not selected:
            messagebox.showwarning("Warning", "No model selected.")
            return
        new_size = self.size_entry.get().strip()
        if not new_size or new_size == "e.g., 256, 256":
            messagebox.showwarning("Warning", "Size cannot be empty.")
            return
        try:
            # Extract all numbers from the input
            numbers = re.findall(r'\d+', new_size)
            if len(numbers) != 2:
                raise ValueError
            size_tuple = tuple(map(int, numbers))
            if any(n <= 0 for n in size_tuple):
                raise ValueError
        except ValueError:
            messagebox.showerror("Error", "Invalid size format. Enter two positive integers separated by a comma (e.g., 256, 256).")
            return

        for item in selected:
            self.models[item]["size"] = size_tuple  # Updating to tuple
            self.models[item]["completed"] = True  # Mark as completed

            # Update the treeview item
            # Remove from current treeview and add to Completed if not already there
            tree.delete(item)
            self.tree_completed.insert("", "end", iid=item, values=(
                item,
                self.models[item]["hashes"]["deep_swapper"]["url"],
                self.models[item]["sources"]["deep_swapper"]["url"],
                self.models[item]["template"],
                self.format_size(self.models[item]["size"])  # Display as "width x height"
            ))

        messagebox.showinfo("Success", "Selected models have been updated and moved to Completed Models.")

    def get_current_tab(self):
        # Get the name of the currently selected tab
        notebook = self.completed_frame.master  # Assuming notebook is the parent of completed_frame
        return notebook.tab(notebook.select(), "text")

    def save_changes(self):
        if not self.dfmpath:
            messagebox.showerror("Error", "DFM Models Path not set.")
            return

        # Building the MODEL_SET dictionary as a string
        models_str = "MODEL_SET : ModelSet =\n{\n"
        for model_key, model_data in self.models.items():
            # Normalize the model_key to use '/' as separator
            prefixed_key = model_key.replace(os.path.sep, '/')

            # Determine if paths are relative or absolute and use resolve_relative_path accordingly
            # Here, assuming paths within the DFM path are relative
            hash_path = model_data["hashes"]["deep_swapper"]["path"]
            source_path = model_data["sources"]["deep_swapper"]["path"]

            # Check if the path is inside the dfmpath to make it relative
            if os.path.commonpath([hash_path, self.dfmpath]) == self.dfmpath:
                rel_hash_path = os.path.relpath(hash_path, self.dfmpath).replace("\\", "/")
                hash_path_str = f"resolve_relative_path('../{rel_hash_path}')"
            else:
                hash_path_str = f'"{hash_path}"'

            if os.path.commonpath([source_path, self.dfmpath]) == self.dfmpath:
                rel_source_path = os.path.relpath(source_path, self.dfmpath).replace("\\", "/")
                source_path_str = f"resolve_relative_path('../{rel_source_path}')"
            else:
                source_path_str = f'"{source_path}"'

            # Manually construct hashes and sources with url=None and path
            hashes_managed_str = (
                "        \"hashes\": {\n"
                "            \"deep_swapper\": {\n"
                "                \"url\": None,\n"
                f"                \"path\": {hash_path_str}\n"
                "            }\n"
                "        },\n"
            )

            sources_managed_str = (
                "        \"sources\": {\n"
                "            \"deep_swapper\": {\n"
                "                \"url\": None,\n"
                f"                \"path\": {source_path_str}\n"
                "            }\n"
                "        },\n"
            )

            # Format the template and size
            template_str = f'        "template": "{model_data["template"]}",\n'
            size_tuple_str = f'        "size": {model_data["size"]}\n'  # Tuple format

            # Concatenate all parts for the current model
            model_entry = f'    "{prefixed_key}": {{\n'
            model_entry += hashes_managed_str
            model_entry += sources_managed_str
            model_entry += template_str
            model_entry += size_tuple_str
            model_entry += "    },\n"

            models_str += model_entry

        models_str += "}\n"

        # Combine all parts into the final content
        file_content = models_str

        # Define the path for the .py file
        python_file_path = os.path.join(self.dfmpath, "updated_models.py")

        try:
            with open(python_file_path, 'w', encoding='utf-8') as f:
                f.write(file_content)
            messagebox.showinfo("Success", f"Changes saved to {python_file_path}")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save changes.\n{e}")
            return

    ### Checksum Methods ###

    def calculate_checksum(self, file_path):
        """Calculate CRC32 checksum for a given file."""
        with open(file_path, 'rb') as f:
            content = f.read()
        return format(zlib.crc32(content), '08x')

    def generate_checksums(self, directory):
        """Generate checksum files for all .dfm files in the directory."""
        for filename in sorted(os.listdir(directory)):
            file_path = os.path.join(directory, filename)

            # Process only .dfm files
            if os.path.isfile(file_path) and filename.lower().endswith('.dfm'):
                hash_file_path = os.path.join(directory, f"{os.path.splitext(filename)[0]}.hash")

                checksum = self.calculate_checksum(file_path)
                with open(hash_file_path, 'w') as hash_file:
                    hash_file.write(checksum)
                print(f"Checksum for '{filename}' written to '{hash_file_path}'")

    def handle_generate_checksums(self):
        """Handler for Generate Checksums button."""
        directory = self.dfmpath
        if not os.path.isdir(directory):
            messagebox.showerror("Error", "Invalid directory path.")
            return
        self.generate_checksums(directory)
        messagebox.showinfo("Success", "Checksums have been generated for all .dfm files.")

    ### Renaming Functionality Begins Here ###

    def get_dfm_files(self):
        folder = self.dfmpath
        if not folder:
            messagebox.showwarning("No Folder Selected", "Please select a folder first.")
            return []

        dfm_files = []
        for root_dir, dirs, files in os.walk(folder):
            for filename in files:
                if filename.lower().endswith(".dfm"):
                    filepath = os.path.join(root_dir, filename)
                    mod_time = os.path.getmtime(filepath)
                    atime = os.path.getatime(filepath)
                    dfm_files.append((filename, mod_time, atime, root_dir))
        
        if not dfm_files:
            messagebox.showinfo("No DFM Files", "No .dfm files found in the selected folder.")
        
        return dfm_files

    def preview_changes(self):
        self.rename_tree.delete(*self.rename_tree.get_children())
        dfm_files = self.get_dfm_files()
        if not dfm_files:
            return

        # Sort files based on modification time (newest first by default)
        dfm_files.sort(key=lambda x: x[1], reverse=True)

        self.rename_preview_list = []
        for original, mod_time, atime, root_dir in dfm_files:
            new_name = original.replace(" ", "_")
            if original != new_name:
                self.rename_preview_list.append((original, new_name, mod_time, atime, root_dir))
                self.rename_tree.insert("", "end", values=(original, new_name))

        if not self.rename_preview_list:
            messagebox.showinfo("No Changes Needed", "No filenames contain spaces to replace.")

    def rename_files(self):
        if not hasattr(self, 'rename_preview_list') or not self.rename_preview_list:
            messagebox.showinfo("No Changes", "There are no changes to apply. Please preview first.")
            return

        confirm = messagebox.askyesno("Confirm Rename", f"Are you sure you want to rename {len(self.rename_preview_list)} files?")
        if not confirm:
            return

        folder = self.dfmpath
        backup_path = os.path.join(folder, self.backup_filename)
        errors = []
        backup_data = {}

        for original, new, mod_time, atime, root_dir in self.rename_preview_list:
            original_path = os.path.join(root_dir, original)
            new_path = os.path.join(root_dir, new)
            try:
                if os.path.exists(new_path):
                    raise FileExistsError(f"Cannot rename '{original}' to '{new}': Destination file already exists.")
                os.rename(original_path, new_path)
                # Restore original modification and access times
                os.utime(new_path, (atime, mod_time))
                
                # Handle checksum files:
                original_hash_path = os.path.join(root_dir, os.path.splitext(original)[0] + ".hash")
                new_hash_path = os.path.join(root_dir, os.path.splitext(new)[0] + ".hash")
                
                if os.path.exists(original_hash_path):
                    # Delete old hash file
                    os.remove(original_hash_path)
                    print(f"Deleted old checksum file: {original_hash_path}")
                
                # Calculate checksum for the renamed file
                checksum = self.calculate_checksum(new_path)
                with open(new_hash_path, 'w') as hash_file:
                    hash_file.write(checksum)
                print(f"Generated checksum for renamed file: '{new}' -> '{new_hash_path}'")

                # Save to backup data (store the rename mapping)
                relative_new_path = os.path.relpath(new_path, folder).replace("\\", "/")
                backup_data[relative_new_path] = {
                    "original": original,
                    "mod_time": mod_time,
                    "atime": atime,
                    "root_dir": root_dir
                }
            except Exception as e:
                errors.append(str(e))

        if backup_data:
            try:
                with open(backup_path, 'w') as backup_file:
                    json.dump(backup_data, backup_file, indent=4)
            except Exception as e:
                messagebox.showerror("Backup Error", f"Failed to create backup file:\n{str(e)}")
                # Optionally, you might want to undo the renames if backup fails
                return

        if errors:
            error_message = "\n".join(errors)
            messagebox.showerror("Errors Occurred", f"Some files could not be renamed:\n{error_message}")
        else:
            messagebox.showinfo("Success", "All files have been renamed and checksums generated successfully.")

        self.load_models()  # Refresh the model list after renaming and checksum generation

    def revert_changes(self):
        folder = self.dfmpath
        if not folder:
            messagebox.showwarning("No Folder Selected", "Please select a folder first.")
            return

        backup_path = os.path.join(folder, self.backup_filename)
        if not os.path.exists(backup_path):
            messagebox.showinfo("No Backup Found", "No backup file found to revert changes.")
            return

        try:
            with open(backup_path, 'r') as backup_file:
                backup_data = json.load(backup_file)
        except Exception as e:
            messagebox.showerror("Backup Error", f"Failed to read backup file:\n{str(e)}")
            return

        if not backup_data:
            messagebox.showinfo("Empty Backup", "Backup file is empty. Nothing to revert.")
            return

        confirm = messagebox.askyesno("Confirm Revert", f"Are you sure you want to revert {len(backup_data)} files?")
        if not confirm:
            return

        errors = []
        for relative_new_path, info in backup_data.items():
            new_path = os.path.join(folder, relative_new_path)
            original = info["original"]
            mod_time = info["mod_time"]
            atime = info["atime"]
            root_dir = info["root_dir"]
            original_path = os.path.join(root_dir, original)

            try:
                if os.path.exists(original_path):
                    raise FileExistsError(f"Cannot revert '{new_path}' to '{original}': Original filename already exists.")
                os.rename(new_path, original_path)
                # Restore original modification and access times
                os.utime(original_path, (atime, mod_time))

                # Handle checksum files:
                new_hash_path = os.path.join(root_dir, os.path.splitext(original)[0] + ".hash")
                original_hash_path = os.path.join(root_dir, os.path.splitext(new_path)[0] + ".hash")
                
                if os.path.exists(original_hash_path):
                    # Delete the checksum file associated with the renamed file
                    os.remove(original_hash_path)
                    print(f"Deleted checksum file: {original_hash_path}")

                # Restore original checksum file if needed
                # Depending on requirements, you might need to recreate the original checksum
                # For now, we'll skip regenerating the original checksum
            except Exception as e:
                errors.append(str(e))

        if errors:
            error_message = "\n".join(errors)
            messagebox.showerror("Errors Occurred", f"Some files could not be reverted:\n{error_message}")
        else:
            messagebox.showinfo("Success", "All files have been reverted successfully.")
            # Remove the backup file after successful revert
            try:
                os.remove(backup_path)
            except Exception as e:
                messagebox.showwarning("Cleanup Warning", f"Could not delete backup file:\n{str(e)}")

        self.rename_changes_post_revert()

    def rename_changes_post_revert(self):
        """Refresh the rename preview after reverting."""
        self.rename_tree.delete(*self.rename_tree.get_children())
        messagebox.showinfo("Revert Completed", "Renaming changes have been reverted.")
        self.load_models()  # Refresh the model list after reverting

    ### Renaming Functionality Ends Here ###

def main():
    root = tk.Tk()
    root.geometry("1200x800")
    root.minsize(800, 600)
    app = DFMModelManager(root)
    root.mainloop()

if __name__ == "__main__":
    main()