diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7d6e842 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Python Debugger: Module", + "type": "debugpy", + "request": "launch", + "module": "pyhasher" + } + ] +} \ No newline at end of file diff --git a/pyhasher/__main__.py b/pyhasher/__main__.py index 19b42e0..da0aa89 100644 --- a/pyhasher/__main__.py +++ b/pyhasher/__main__.py @@ -1,17 +1,24 @@ # pyhasher/__main__.py -# Example import assuming your main logic is in a 'main' function -# within a 'app' module in your 'pyhasher.core' package. -# from pyhasher.core.app import main as start_application -# -# Or, if you have a function in pyhasher.core.core: -# from pyhasher.core.core import main_function +import tkinter as tk +from pyhasher.gui.gui import PyHasherApp def main(): - print(f"Running PyHasher...") - # Placeholder: Replace with your application's entry point - # Example: start_application() - print("To customize, edit 'pyhasher/__main__.py' and your core modules.") + """ + Main function to initialize and run the PyHasher application. + + This function sets up the main Tkinter window and starts the GUI application. + """ + # Create the main window + root = tk.Tk() + + # Create an instance of the application + app = PyHasherApp(root) + + # Start the Tkinter event loop + root.mainloop() if __name__ == "__main__": - main() + # This block allows the script to be run directly, + # though the primary entry point is via 'python -m pyhasher' + main() \ No newline at end of file diff --git a/pyhasher/core/core.py b/pyhasher/core/core.py index e69de29..d2623df 100644 --- a/pyhasher/core/core.py +++ b/pyhasher/core/core.py @@ -0,0 +1,67 @@ +# pyhasher/core/core.py + +import hashlib +import zlib + +# Define a constant for the chunk size to read from the file. +# This helps in processing large files without loading them entirely into memory. +BUFFER_SIZE = 65536 # 64KB + +def calculate_hashes_for_file(file_path: str) -> dict: + """ + Calculates various hashes for a given file. + + Includes checksums (CRC32, Adler-32) and cryptographic hashes + (MD5, SHA-1, SHA-2, SHA-3, BLAKE2). + + Args: + file_path: The absolute path to the file. + + Returns: + A dictionary containing the calculated hashes. + Example: {'CRC32': '...', 'MD5': '...', ...} + + Raises: + IOError: If the file cannot be read. + """ + # Initialize checksums + crc32_val = 0 + adler32_val = 1 # Adler-32 starts with a value of 1 + + # Initialize cryptographic hashers + hashers = { + "MD5": hashlib.md5(), + "SHA-1": hashlib.sha1(), + "SHA-256": hashlib.sha256(), + "SHA-384": hashlib.sha384(), + "SHA-512": hashlib.sha512(), + "SHA3-256": hashlib.sha3_256(), + "BLAKE2b": hashlib.blake2b(), + } + + with open(file_path, "rb") as f: + while True: + # Read the file in chunks + data = f.read(BUFFER_SIZE) + if not data: + break + + # Update each cryptographic hasher + for hasher in hashers.values(): + hasher.update(data) + + # Update checksums + crc32_val = zlib.crc32(data, crc32_val) + adler32_val = zlib.adler32(data, adler32_val) + + # Prepare the results dictionary, starting with checksums + results = { + "CRC32": f"{crc32_val:08x}", + "Adler-32": f"{adler32_val:08x}", + } + + # Add cryptographic hashes to the results + for name, hasher in hashers.items(): + results[name] = hasher.hexdigest() + + return results \ No newline at end of file diff --git a/pyhasher/gui/gui.py b/pyhasher/gui/gui.py index e69de29..a61e303 100644 --- a/pyhasher/gui/gui.py +++ b/pyhasher/gui/gui.py @@ -0,0 +1,130 @@ +# pyhasher/gui/gui.py + +import tkinter as tk +from tkinter import filedialog, messagebox +import os + +# Import the core logic function +from pyhasher.core.core import calculate_hashes_for_file + +# Defines the order in which hashes will be displayed in the GUI. +# This must match the keys returned by the core function. +HASH_DISPLAY_ORDER = ( + "CRC32", + "Adler-32", + "MD5", + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-256", + "BLAKE2b", +) + +class PyHasherApp: + """ + The main GUI application class for PyHasher. + It handles the user interface and interactions. + """ + def __init__(self, root_window): + """ + Initialize the application. + + Args: + root_window: The main tkinter window. + """ + self.root = root_window + self.root.title("PyHasher") + self.root.geometry("1024x400") # Increased height for more hashes + self.file_path = tk.StringVar() + + # --- Main frame --- + main_frame = tk.Frame(self.root, padx=10, pady=10) + main_frame.pack(fill=tk.BOTH, expand=True) + + # --- File selection widgets --- + self._create_file_selection_widgets(main_frame) + + # --- Hash results widgets --- + self._create_hash_display_widgets(main_frame) + + # --- Action buttons --- + self._create_action_buttons(main_frame) + + def _create_file_selection_widgets(self, parent_frame): + """Creates widgets for file selection.""" + file_frame = tk.Frame(parent_frame) + file_frame.pack(fill=tk.X, pady=(0, 10)) + + file_label = tk.Label(file_frame, text="File:") + file_label.pack(side=tk.LEFT, padx=(0, 5)) + + file_entry = tk.Entry(file_frame, textvariable=self.file_path, state="readonly") + file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) + + browse_button = tk.Button(file_frame, text="Browse...", command=self.select_file) + browse_button.pack(side=tk.LEFT, padx=(5, 0)) + + def _create_hash_display_widgets(self, parent_frame): + """Creates labels and entry boxes for displaying hash results.""" + results_frame = tk.Frame(parent_frame) + results_frame.pack(fill=tk.BOTH, expand=True) + + # Dictionary to hold hash names and their corresponding tk.StringVar + self.hash_vars = {name: tk.StringVar() for name in HASH_DISPLAY_ORDER} + + # Create labels and read-only entry widgets for each hash type + for i, name in enumerate(HASH_DISPLAY_ORDER): + label = tk.Label(results_frame, text=f"{name}:") + label.grid(row=i, column=0, sticky="w", pady=2) + + entry = tk.Entry(results_frame, textvariable=self.hash_vars[name], state="readonly") + entry.grid(row=i, column=1, sticky="ew", pady=2, padx=(5, 0)) + + # Configure the grid to expand the entry column + results_frame.columnconfigure(1, weight=1) + + def _create_action_buttons(self, parent_frame): + """Creates the main action buttons.""" + button_frame = tk.Frame(parent_frame) + button_frame.pack(fill=tk.X, pady=(10, 0), side=tk.BOTTOM) + + calculate_button = tk.Button( + button_frame, + text="Calculate Hashes", + command=self.calculate_hashes + ) + calculate_button.pack(side=tk.RIGHT) + + def select_file(self): + """Opens a file dialog to select a file and clears old results.""" + path = filedialog.askopenfilename() + if path: + self.file_path.set(path) + # Clear previous results when a new file is selected + for var in self.hash_vars.values(): + var.set("") + + def calculate_hashes(self): + """ + Gets the file path, calls the core function to calculate hashes, + and displays the results in the GUI. + """ + current_file_path = self.file_path.get() + if not current_file_path or not os.path.exists(current_file_path): + messagebox.showerror("Error", "Please select a valid file first.") + return + + try: + # Call the decoupled core function to do the heavy lifting + all_hashes = calculate_hashes_for_file(current_file_path) + + # Update the GUI with the results from the core function + for name, value in all_hashes.items(): + if name in self.hash_vars: + self.hash_vars[name].set(value) + + except IOError as e: + messagebox.showerror("Error", f"Could not read file: {e}") + except Exception as e: + messagebox.showerror("Error", f"An unexpected error occurred: {e}") \ No newline at end of file