From 5fd37e12fe93835eef1d065d3b768aa1f1233c6f Mon Sep 17 00:00:00 2001 From: Phill Katz Date: Sat, 13 Jul 2024 12:47:44 -0400 Subject: [PATCH] Added new features and functionality to main code. Also updated README and USER_GUIDE Added new features and functionality to app. Also updated README and USER_GUIDE to reflect the changes. These new added functions will allow for a more controlled and precise application of the name change operation. - Added ability to choose top-level folders only, subfolders only, and files only or any combination to be renamed - Added the ability to control the depth of subfolders you want to be effected by the name changing operation - Added a cancel button to cancel the current operation - The app now refreshes when the operation is completed or canceled --- README.md | 20 ++----- USER_GUIDE.md | 56 ++++++++++--------- fatx360.py | 145 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 160 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index af5d229..80fa43c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # FATX360 -FATX360 is a Python application with a graphical user interface (GUI) that allows users to easily rename files and folders to be compatible with the FATX file system. This tool is particularly useful for users working with Xbox systems or other platforms that use the FATX file system. +FATX360 is a Python application with a graphical user interface (GUI) that allows users to easily rename files and folders to be compatible with the FATX file system. This tool is particularly useful for users working with Xbox 360 systems or other platforms that use the FATX file system. ## Features - Select a directory to rename files and folders - Choose specific files or folders for renaming - "Select All" functionality for easy batch selection -- Option to rename folders only without affecting their contents -- Rename items to be FATX compatible (42 character limit, restricted character set) +- Option to rename top-level folders only +- Option to rename subfolders with customizable depth +- Option to rename files - Copy renamed items to a new directory, preserving the original files - Progress bar to track the renaming process +- Cancel operation functionality - Error handling for various scenarios ## Installation @@ -39,14 +41,6 @@ FATX360 is a Python application with a graphical user interface (GUI) that allow For detailed usage instructions, please refer to the [User Guide](USER_GUIDE.md). -## Quick Start - -1. Click "Select Directory" to choose the folder containing files to rename. -2. Select files/folders in the list (use "Select All" for batch selection). -3. (Optional) Check "Rename folders only" to leave file names unchanged. -4. Click "Rename Selected" and choose a destination for the renamed items. -5. Monitor the progress bar and wait for the success message. - ## Contributing Contributions to FATX360 are welcome! Please feel free to submit a Pull Request. @@ -60,10 +54,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - Thanks to all contributors who have helped to improve this tool. - Special thanks to the Python community for providing excellent documentation and libraries. -## Contact - -If you have any questions, feel free to open an issue or contact the repository owner. - --- Happy renaming! diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 5775fb0..58236dd 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -2,30 +2,24 @@ ## Table of Contents 1. [Introduction](#introduction) -2. [Installation](#installation) -3. [Getting Started](#getting-started) -4. [Features](#features) -5. [Usage Guide](#usage-guide) -6. [Troubleshooting](#troubleshooting) -7. [FAQs](#faqs) +2. [Getting Started](#getting-started) +3. [Features](#features) +4. [Usage Guide](#usage-guide) +5. [Troubleshooting](#troubleshooting) +6. [FAQs](#faqs) ## Introduction -FATX360 is a Python application with a graphical user interface (GUI) that allows users to rename files and folders to be compatible with the FATX file system. This tool is particularly useful for users working with Xbox systems or other platforms that use the FATX file system. - -## Installation - -1. Ensure you have Python 3.x installed on your system. You can download it from [python.org](https://www.python.org/downloads/). -2. Save the `fatx360.py` script to your local machine. -3. No additional libraries are required as the application uses Python's standard libraries. +FATX360 is a Python application with a graphical user interface (GUI) that allows users to rename files and folders to be compatible with the FATX file system. This tool is particularly useful for users working with Xbox 360 systems or other platforms that use the FATX file system. ## Getting Started -To run the FATX360: +To run FATX360: -1. Open a command prompt or terminal. -2. Navigate to the directory containing `fatx360.py`. -3. Run the command: `python fatx360.py` +1. Ensure you have Python 3.x installed on your system. +2. Open a command prompt or terminal. +3. Navigate to the directory containing `fatx360.py`. +4. Run the command: `python fatx360.py` The application window should appear, ready for use. @@ -34,10 +28,12 @@ The application window should appear, ready for use. - Select a directory to rename files and folders. - Choose specific files or folders for renaming. - "Select All" functionality to easily select or deselect all items. -- Option to rename folders only without affecting their contents. -- Rename items to be FATX compatible (42 character limit, restricted character set). +- Option to rename only top-level folders. +- Option to rename subfolders with customizable depth. +- Option to rename files. - Copy renamed items to a new directory, preserving the original files. - Progress bar to track the renaming process. +- Cancel button to stop the operation mid-process. - Error handling for various scenarios (permission issues, file not found, etc.). ## Usage Guide @@ -51,7 +47,10 @@ The application window should appear, ready for use. - Click the "Select All" button to select or deselect all items at once. 3. **Set Options**: - - Check "Rename folders only" if you want to rename only folders without affecting their contents. + - Check "Rename top-level folders" to rename only the main folders. + - Check "Rename subfolders" to rename folders within the main folders. + - Use the depth slider to specify how many levels of subfolders to rename. + - Check "Rename files" to rename individual files. 4. **Start Renaming**: - Click the "Rename Selected" button. @@ -59,8 +58,12 @@ The application window should appear, ready for use. 5. **Monitor Progress**: - The progress bar will show the status of the renaming process. + - The label next to the progress bar shows the number of processed items and total items. + +6. **Cancel Operation** (if needed): + - Click the "Cancel" button to stop the renaming process. -6. **Review Results**: +7. **Review Results**: - Once complete, a success message will appear. - Check the "RENAMED" folder in your chosen destination directory for the renamed items. @@ -68,8 +71,9 @@ The application window should appear, ready for use. - **Permission Error**: Ensure you have the necessary permissions to access the selected directory and create new files/folders. - **Files Not Appearing**: Make sure the selected directory contains files or folders. -- **Renaming Not Starting**: Verify that you've selected at least one item to rename. +- **Renaming Not Starting**: Verify that you've selected at least one item to rename and set at least one renaming option. - **Application Not Starting**: Confirm that Python is correctly installed and the script is in the correct directory. +- **Operation Cancelled**: If you cancel the operation, the interface will reset. You can start a new operation as needed. ## FAQs @@ -82,10 +86,10 @@ The application window should appear, ready for use. 3. **Q: Can I undo the renaming process?** A: The renaming process creates copies, so your original files remain unchanged. To "undo", simply use the original files. -4. **Q: Why can't I select a file or folder?** - A: Ensure you have permission to access the file/folder. Also, check if the "Rename folders only" option is selected when trying to select files. +4. **Q: What happens if I don't select any renaming options?** + A: If no options are selected, all folders, subfolders, and files will be renamed to be FATX compatible. -5. **Q: The application is slow with many files. What can I do?** - A: For large directories, consider renaming in smaller batches by selecting fewer items at a time. +5. **Q: How does the subfolder depth slider work?** + A: The slider lets you choose how many levels of subfolders to rename. A value of 1 renames only immediate subfolders, while higher values rename deeper levels of subfolders. For any additional questions or issues, please refer to the script comments or contact the developer. diff --git a/fatx360.py b/fatx360.py index d9a0a73..f590450 100644 --- a/fatx360.py +++ b/fatx360.py @@ -27,13 +27,15 @@ class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) self.master = master - self.master.title("FATX360 v1.2") - self.master.geometry("500x400") + self.master.title("FATX360 v1.3") + self.master.geometry("500x550") self.pack(fill=tk.BOTH, expand=True) - self.create_widgets() self.all_selected = False self.total_items = 0 self.processed_items = 0 + self.cancel_flag = False + self.max_depth = 10 # Maximum depth for the slider + self.create_widgets() def create_widgets(self): self.create_menu() @@ -59,12 +61,39 @@ def create_widgets(self): options_frame = ttk.Frame(self) options_frame.pack(fill=tk.X, padx=10, pady=5) - self.folders_only_var = tk.BooleanVar() - self.folders_only_check = ttk.Checkbutton(options_frame, text="Rename folders only", variable=self.folders_only_var) - self.folders_only_check.pack(side=tk.LEFT) - - self.rename_button = ttk.Button(options_frame, text="Rename Selected", command=self.rename_selected) - self.rename_button.pack(side=tk.RIGHT) + self.top_level_var = tk.BooleanVar() + self.top_level_check = ttk.Checkbutton(options_frame, text="Rename top-level folders", + variable=self.top_level_var) + self.top_level_check.pack(side=tk.TOP, anchor=tk.W) + + self.subfolders_var = tk.BooleanVar() + self.subfolders_check = ttk.Checkbutton(options_frame, text="Rename subfolders", + variable=self.subfolders_var, + command=self.toggle_depth_slider) + self.subfolders_check.pack(side=tk.TOP, anchor=tk.W) + + # Depth slider + self.depth_frame = ttk.Frame(options_frame) + self.depth_frame.pack(side=tk.TOP, fill=tk.X, padx=20, pady=5) + self.depth_label = ttk.Label(self.depth_frame, text="Subfolder depth:") + self.depth_label.pack(side=tk.LEFT) + self.depth_var = tk.IntVar(value=self.max_depth) + self.depth_slider = ttk.Scale(self.depth_frame, from_=1, to=self.max_depth, + orient=tk.HORIZONTAL, variable=self.depth_var, + length=200, command=self.update_depth_label) + self.depth_slider.pack(side=tk.LEFT, fill=tk.X, expand=True) + self.depth_value_label = ttk.Label(self.depth_frame, text=str(self.max_depth)) + self.depth_value_label.pack(side=tk.LEFT) + self.depth_frame.pack_forget() # Initially hidden + + self.files_var = tk.BooleanVar() + self.files_check = ttk.Checkbutton(options_frame, text="Rename files", + variable=self.files_var) + self.files_check.pack(side=tk.TOP, anchor=tk.W) + + self.rename_button = ttk.Button(options_frame, text="Rename Selected", + command=self.rename_selected) + self.rename_button.pack(side=tk.TOP, pady=5) progress_frame = ttk.Frame(self) progress_frame.pack(fill=tk.X, padx=10, pady=5) @@ -75,6 +104,9 @@ def create_widgets(self): self.progress_label = ttk.Label(progress_frame, text="0 / 0") self.progress_label.pack(side=tk.RIGHT) + self.cancel_button = ttk.Button(self, text="Cancel", command=self.cancel_operation, state=tk.DISABLED) + self.cancel_button.pack(pady=5) + def create_menu(self): menubar = tk.Menu(self.master) self.master.config(menu=menubar) @@ -111,6 +143,15 @@ def toggle_select_all(self): self.select_all_button.config(text="Deselect All") self.all_selected = not self.all_selected + def toggle_depth_slider(self): + if self.subfolders_var.get(): + self.depth_frame.pack(side=tk.TOP, fill=tk.X, padx=20, pady=5) + else: + self.depth_frame.pack_forget() + + def update_depth_label(self, *args): + self.depth_value_label.config(text=str(self.depth_var.get())) + def rename_selected(self): selected_indices = self.listbox.curselection() selected_items = [self.listbox.get(i) for i in selected_indices] @@ -124,6 +165,8 @@ def rename_selected(self): self.progress['value'] = 0 self.rename_button['state'] = 'disabled' + self.cancel_button['state'] = 'normal' + self.cancel_flag = False self.total_items = self.count_total_items(selected_items) self.processed_items = 0 @@ -149,15 +192,18 @@ def rename_items_thread(self, items, dest_dir): os.makedirs(renamed_dir, exist_ok=True) except PermissionError: self.show_error("Permission Error", "Cannot create the RENAMED directory.") + self.finish_operation() return for item in items: + if self.cancel_flag: + return # Exit the loop if cancelled try: full_path = os.path.join(self.directory, item) if os.path.isdir(full_path): - self.process_directory(full_path, renamed_dir) - elif not self.folders_only_var.get(): - self.process_file(full_path, renamed_dir) + self.process_directory(full_path, renamed_dir, self.top_level_var.get(), self.subfolders_var.get(), self.files_var.get()) + else: + self.process_file(full_path, renamed_dir, self.files_var.get()) except PermissionError: self.show_error("Permission Error", f"Cannot access {item}.") except shutil.Error as e: @@ -165,23 +211,43 @@ def rename_items_thread(self, items, dest_dir): except OSError as e: self.show_error("OS Error", f"Error processing {item}: {str(e)}") - self.show_success("Rename Complete", "Selected items have been renamed and copied to the RENAMED folder.") + if not self.cancel_flag: + self.show_success("Rename Complete", "Selected items have been renamed and copied to the RENAMED folder.") + self.finish_operation() - def process_directory(self, src_dir, dest_parent_dir): - new_dir_name = make_fatx_compatible(os.path.basename(src_dir)) + def process_directory(self, src_dir, dest_parent_dir, rename_top_level, rename_subfolders, rename_files, current_depth=0): + new_dir_name = make_fatx_compatible(os.path.basename(src_dir)) if rename_top_level or (rename_subfolders and current_depth < self.depth_var.get()) else os.path.basename(src_dir) new_dir_path = os.path.join(dest_parent_dir, new_dir_name) os.makedirs(new_dir_path, exist_ok=True) for root, dirs, files in os.walk(src_dir): + if self.cancel_flag: + return rel_path = os.path.relpath(root, src_dir) new_root = os.path.join(new_dir_path, rel_path) + # Rename subfolders if option is selected and within depth + if rename_subfolders and current_depth < self.depth_var.get() and root != src_dir: + new_root = os.path.join(os.path.dirname(new_root), make_fatx_compatible(os.path.basename(root))) + + os.makedirs(new_root, exist_ok=True) + for file in files: + if self.cancel_flag: + return src_file = os.path.join(root, file) - self.process_file(src_file, new_root) + self.process_file(src_file, new_root, rename_files) + + # Process subdirectories + for dir_name in dirs: + full_dir_path = os.path.join(root, dir_name) + self.process_directory(full_dir_path, new_root, False, rename_subfolders, rename_files, current_depth + 1) - def process_file(self, src_file, dest_dir): - new_name = make_fatx_compatible(os.path.basename(src_file)) + # We only want to process the top-level of this directory, so break the loop + break + + def process_file(self, src_file, dest_dir, rename_file): + new_name = make_fatx_compatible(os.path.basename(src_file)) if rename_file else os.path.basename(src_file) new_path = os.path.join(dest_dir, new_name) shutil.copy2(src_file, new_path) self.processed_items += 1 @@ -191,18 +257,57 @@ def update_progress(self): progress_value = (self.processed_items / self.total_items) * 100 self.progress['value'] = progress_value self.update_progress_label() - if progress_value >= 100: - self.rename_button['state'] = 'normal' def update_progress_label(self): self.progress_label.config(text=f"{self.processed_items} / {self.total_items}") + def cancel_operation(self): + self.cancel_flag = True + self.cancel_button['state'] = 'disabled' + self.show_info("Operation Cancelled", "The renaming operation was cancelled.") + self.reset_interface() + + def finish_operation(self): + self.rename_button['state'] = 'normal' + self.cancel_button['state'] = 'disabled' + self.cancel_flag = False + self.reset_interface() + + def reset_interface(self): + # Reset progress bar + self.progress['value'] = 0 + self.progress_label.config(text="0 / 0") + + # Reset selection + self.listbox.selection_clear(0, tk.END) + self.all_selected = False + self.select_all_button.config(text="Select All") + + # Reset checkboxes + self.top_level_var.set(False) + self.subfolders_var.set(False) + self.files_var.set(False) + + # Hide depth slider + self.depth_frame.pack_forget() + + # Reset depth slider value + self.depth_var.set(self.max_depth) + self.update_depth_label() + + # Reset counters + self.total_items = 0 + self.processed_items = 0 + def show_error(self, title, message): self.master.after(0, lambda: messagebox.showerror(title, message)) def show_success(self, title, message): self.master.after(0, lambda: messagebox.showinfo(title, message)) + def show_info(self, title, message): + self.master.after(0, lambda: messagebox.showinfo(title, message)) + root = tk.Tk() app = Application(master=root) app.mainloop()