From f8a942bf51f44124eb96a220b2f38f9f81421e23 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Mon, 22 Sep 2025 14:38:14 -0500 Subject: [PATCH] reorganized file and folder structure to reflect responsibilities --- README.md | 2 +- src/__init__.py | 4 +- src/core/__init__.py | 4 + src/{ => core}/adb_manager.py | 132 +------------------- src/gui/__init__.py | 2 +- src/gui/components/__init__.py | 4 + src/gui/{ => components}/file_browser.py | 0 src/gui/{ => components}/ui_components.py | 4 +- src/gui/dialogs/__init__.py | 4 + src/gui/{ => dialogs}/dialog_manager.py | 0 src/gui/{ => dialogs}/license_agreement.py | 0 src/gui/handlers/__init__.py | 4 + src/gui/{ => handlers}/animation_handler.py | 0 src/gui/main_window.py | 28 ++--- src/main.py | 8 +- src/managers/__init__.py | 4 + src/{gui => managers}/device_manager.py | 4 +- src/{gui => managers}/transfer_manager.py | 8 +- src/utils/__init__.py | 4 + src/{ => utils}/file_deduplication.py | 0 20 files changed, 58 insertions(+), 158 deletions(-) create mode 100644 src/core/__init__.py rename src/{ => core}/adb_manager.py (89%) create mode 100644 src/gui/components/__init__.py rename src/gui/{ => components}/file_browser.py (100%) rename src/gui/{ => components}/ui_components.py (98%) create mode 100644 src/gui/dialogs/__init__.py rename src/gui/{ => dialogs}/dialog_manager.py (100%) rename src/gui/{ => dialogs}/license_agreement.py (100%) create mode 100644 src/gui/handlers/__init__.py rename src/gui/{ => handlers}/animation_handler.py (100%) create mode 100644 src/managers/__init__.py rename src/{gui => managers}/device_manager.py (98%) rename src/{gui => managers}/transfer_manager.py (98%) create mode 100644 src/utils/__init__.py rename src/{ => utils}/file_deduplication.py (100%) diff --git a/README.md b/README.md index fef39e0..4b90118 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ A simple Windows GUI tool to pull folders from an Android device using ADB (Andr 2. Run the application: ```sh - poetry run python3 src/main.py + poetry run python -m src.main ``` 3. Follow the on-screen instructions: diff --git a/src/__init__.py b/src/__init__.py index eb1a62a..db89293 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,9 +3,8 @@ Android File Handler - Package Initialization Exposes the main components of the Android file handler application. """ -from .adb_manager import ( +from .core.adb_manager import ( ADBManager, - LinuxMTPManager, get_adb_binary_path, is_adb_available, get_platform_type, @@ -14,7 +13,6 @@ from .gui import AndroidFileHandlerGUI, main __all__ = [ "ADBManager", - "LinuxMTPManager", "AndroidFileHandlerGUI", "get_adb_binary_path", "is_adb_available", diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..c8292cf --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1,4 @@ +""" +Core Package +Core functionality and business logic components. +""" \ No newline at end of file diff --git a/src/adb_manager.py b/src/core/adb_manager.py similarity index 89% rename from src/adb_manager.py rename to src/core/adb_manager.py index 5e0be2a..94153e8 100644 --- a/src/adb_manager.py +++ b/src/core/adb_manager.py @@ -19,9 +19,9 @@ from typing import Optional import hashlib try: - from file_deduplication import FileDeduplicator + from utils.file_deduplication import FileDeduplicator except ImportError: - from .file_deduplication import FileDeduplicator + from ..utils.file_deduplication import FileDeduplicator def get_executable_directory() -> str: """Get the directory containing the executable or script.""" @@ -1029,133 +1029,7 @@ class ADBManager: return self.deduplicator.check_files_identical( local_path, remote_path, self.run_adb_command, algorithm ) - - -class LinuxMTPManager: - """Manages MTP operations on Linux systems.""" - - def __init__(self): - self.mount_point = "/tmp/android_mtp" - - def mount_mtp_device(self) -> Optional[str]: - """Mount MTP device to filesystem using jmtpfs.""" - try: - # Create mount point - os.makedirs(self.mount_point, exist_ok=True) - - # Check if already mounted - result = subprocess.run( - ["mountpoint", self.mount_point], capture_output=True, text=True - ) - if result.returncode == 0: - return self.mount_point - - # First, try to unmount any existing GVFS MTP mounts - self._unmount_gvfs_mtp() - - # Kill any existing MTP processes that might be interfering - subprocess.run(["pkill", "-f", "gvfs-mtp"], capture_output=True) - subprocess.run(["pkill", "-f", "jmtpfs"], capture_output=True) - - # Wait a moment for processes to clean up - time.sleep(1) - - # Mount using jmtpfs - result = subprocess.run( - ["jmtpfs", self.mount_point], capture_output=True, text=True - ) - if result.returncode == 0: - return self.mount_point - else: - print(f"Failed to mount MTP device: {result.stderr}") - return None - except Exception as e: - print(f"Error mounting MTP device: {e}") - return None - - def _unmount_gvfs_mtp(self): - """Unmount any GVFS MTP mounts.""" - try: - # Find GVFS MTP mounts - result = subprocess.run(["mount"], capture_output=True, text=True) - for line in result.stdout.splitlines(): - if "gvfs" in line and "mtp" in line: - # Extract mount point from mount line - parts = line.split() - if len(parts) >= 3: - mount_point = parts[2] - subprocess.run( - ["fusermount", "-u", mount_point], capture_output=True - ) - - # Also try to unmount common GVFS locations - gvfs_locations = ["/run/user/*/gvfs/mtp*", "/media/*", "~/.gvfs/mtp*"] - - for location_pattern in gvfs_locations: - result = subprocess.run( - ["find", "/run/user", "-name", "mtp*", "-type", "d"], - capture_output=True, - text=True, - ) - for mount_point in result.stdout.strip().split("\n"): - if mount_point: - subprocess.run( - ["fusermount", "-u", mount_point], capture_output=True - ) - - except Exception as e: - print(f"Warning: Could not unmount GVFS MTP: {e}") - - def unmount_mtp_device(self) -> bool: - """Unmount MTP device.""" - try: - subprocess.run(["fusermount", "-u", self.mount_point], check=True) - return True - except subprocess.CalledProcessError as e: - print(f"Failed to unmount: {e}") - return False - - def find_gvfs_mtp_mount(self) -> Optional[str]: - """Find existing GVFS MTP mount point.""" - try: - # Check common GVFS mount locations (Linux only) - if sys.platform.startswith("linux"): - try: - if hasattr(os, "getuid"): - user_id = os.getuid() # type: ignore - gvfs_patterns = [ - f"/run/user/{user_id}/gvfs/mtp*", - "/media/*android*", - "/media/*MTP*", - ] - else: - # Fallback if getuid is not available - gvfs_patterns = [ - "/run/user/*/gvfs/mtp*", - "/media/*android*", - "/media/*MTP*", - ] - except (AttributeError, OSError): - # Fallback if getuid is not available or fails - gvfs_patterns = [ - "/run/user/*/gvfs/mtp*", - "/media/*android*", - "/media/*MTP*", - ] - - for pattern in gvfs_patterns: - matches = glob.glob(pattern) - if matches: - # Return the first valid mount point - for mount in matches: - if os.path.isdir(mount): - return mount - return None - except Exception as e: - print(f"Error finding GVFS mount: {e}") - return None - - + # Helper functions for standalone usage def is_adb_available() -> bool: """Check if ADB binary is available using the centralized resolver.""" diff --git a/src/gui/__init__.py b/src/gui/__init__.py index 73fa82f..9d32934 100644 --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -4,7 +4,7 @@ Provides modular GUI components for the Android file transfer application. """ from .main_window import AndroidFileHandlerGUI, main -from .file_browser import AndroidFileBrowser +from .components.file_browser import AndroidFileBrowser __all__ = [ "AndroidFileHandlerGUI", diff --git a/src/gui/components/__init__.py b/src/gui/components/__init__.py new file mode 100644 index 0000000..79f09ea --- /dev/null +++ b/src/gui/components/__init__.py @@ -0,0 +1,4 @@ +""" +GUI Components Package +Reusable UI components and widgets. +""" \ No newline at end of file diff --git a/src/gui/file_browser.py b/src/gui/components/file_browser.py similarity index 100% rename from src/gui/file_browser.py rename to src/gui/components/file_browser.py diff --git a/src/gui/ui_components.py b/src/gui/components/ui_components.py similarity index 98% rename from src/gui/ui_components.py rename to src/gui/components/ui_components.py index 600354a..6f2546b 100644 --- a/src/gui/ui_components.py +++ b/src/gui/components/ui_components.py @@ -8,9 +8,9 @@ from tkinter import messagebox from typing import Callable, Optional try: - from .license_agreement import LicenseAgreementFrame, check_license_agreement + from ..dialogs.license_agreement import LicenseAgreementFrame, check_license_agreement except ImportError: - from license_agreement import LicenseAgreementFrame, check_license_agreement + from gui.dialogs.license_agreement import LicenseAgreementFrame, check_license_agreement class PathSelectorFrame: diff --git a/src/gui/dialogs/__init__.py b/src/gui/dialogs/__init__.py new file mode 100644 index 0000000..4809958 --- /dev/null +++ b/src/gui/dialogs/__init__.py @@ -0,0 +1,4 @@ +""" +GUI Dialogs Package +Dialog windows and user interaction components. +""" \ No newline at end of file diff --git a/src/gui/dialog_manager.py b/src/gui/dialogs/dialog_manager.py similarity index 100% rename from src/gui/dialog_manager.py rename to src/gui/dialogs/dialog_manager.py diff --git a/src/gui/license_agreement.py b/src/gui/dialogs/license_agreement.py similarity index 100% rename from src/gui/license_agreement.py rename to src/gui/dialogs/license_agreement.py diff --git a/src/gui/handlers/__init__.py b/src/gui/handlers/__init__.py new file mode 100644 index 0000000..a6733e8 --- /dev/null +++ b/src/gui/handlers/__init__.py @@ -0,0 +1,4 @@ +""" +GUI Handlers Package +Event handlers and animation controllers. +""" \ No newline at end of file diff --git a/src/gui/animation_handler.py b/src/gui/handlers/animation_handler.py similarity index 100% rename from src/gui/animation_handler.py rename to src/gui/handlers/animation_handler.py diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 9213ed5..7198c8e 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -10,33 +10,33 @@ from tkinter import messagebox, filedialog try: # Try relative import first (when used as module) - from ..adb_manager import ADBManager, is_adb_available + from ..core.adb_manager import ADBManager, is_adb_available except ImportError: # Fall back to direct import (when run directly) - from adb_manager import ADBManager, is_adb_available + from core.adb_manager import ADBManager, is_adb_available try: # Try relative imports first - from .file_browser import AndroidFileBrowser - from .animation_handler import AnimationHandler - from .device_manager import DeviceManager - from .dialog_manager import DialogManager - from .transfer_manager import TransferManager - from .ui_components import ( + from .components.file_browser import AndroidFileBrowser + from .handlers.animation_handler import AnimationHandler + from .dialogs.dialog_manager import DialogManager + from .components.ui_components import ( PathSelectorFrame, DirectionSelector, StatusLabel, TransferButton, LicenseManager ) + from ..managers.device_manager import DeviceManager + from ..managers.transfer_manager import TransferManager except ImportError: # Fall back to direct imports - from src.gui.file_browser import AndroidFileBrowser - from src.gui.animation_handler import AnimationHandler - from src.gui.device_manager import DeviceManager - from src.gui.dialog_manager import DialogManager - from src.gui.transfer_manager import TransferManager - from src.gui.ui_components import ( + from components.file_browser import AndroidFileBrowser + from handlers.animation_handler import AnimationHandler + from dialogs.dialog_manager import DialogManager + from components.ui_components import ( PathSelectorFrame, DirectionSelector, StatusLabel, TransferButton, LicenseManager ) + from managers.device_manager import DeviceManager + from managers.transfer_manager import TransferManager class AndroidFileHandlerGUI(tk.Tk): diff --git a/src/main.py b/src/main.py index 88a95c7..3fdd9b0 100644 --- a/src/main.py +++ b/src/main.py @@ -4,8 +4,12 @@ Android File Handler - Main Entry Point Simple entry point to launch the Android file transfer application. """ -from gui.main_window import main -from gui.license_agreement import run_windows_first_run_if_needed +try: + from gui.main_window import main + from gui.dialogs.license_agreement import run_windows_first_run_if_needed +except ImportError: + from .gui.main_window import main + from .gui.dialogs.license_agreement import run_windows_first_run_if_needed if __name__ == "__main__": diff --git a/src/managers/__init__.py b/src/managers/__init__.py new file mode 100644 index 0000000..7d1505b --- /dev/null +++ b/src/managers/__init__.py @@ -0,0 +1,4 @@ +""" +Managers Package +Business logic managers for device and transfer operations. +""" \ No newline at end of file diff --git a/src/gui/device_manager.py b/src/managers/device_manager.py similarity index 98% rename from src/gui/device_manager.py rename to src/managers/device_manager.py index 92c5e1d..045cf62 100644 --- a/src/gui/device_manager.py +++ b/src/managers/device_manager.py @@ -8,9 +8,9 @@ from tkinter import messagebox from typing import Optional, Callable try: - from ..adb_manager import ADBManager, is_adb_available + from ..core.adb_manager import ADBManager, is_adb_available except ImportError: - from adb_manager import ADBManager, is_adb_available + from core.adb_manager import ADBManager, is_adb_available class DeviceManager: diff --git a/src/gui/transfer_manager.py b/src/managers/transfer_manager.py similarity index 98% rename from src/gui/transfer_manager.py rename to src/managers/transfer_manager.py index a65982a..b1cd7f8 100644 --- a/src/gui/transfer_manager.py +++ b/src/managers/transfer_manager.py @@ -9,12 +9,12 @@ from typing import Optional, Callable, Tuple, Dict, Any try: from .device_manager import DeviceManager - from .animation_handler import AnimationHandler - from .dialog_manager import DialogManager + from ..gui.handlers.animation_handler import AnimationHandler + from ..gui.dialogs.dialog_manager import DialogManager except ImportError: from device_manager import DeviceManager - from animation_handler import AnimationHandler - from dialog_manager import DialogManager + from gui.handlers.animation_handler import AnimationHandler + from gui.dialogs.dialog_manager import DialogManager class TransferManager: diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..ec72994 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,4 @@ +""" +Utils Package +Utility functions and helper modules. +""" \ No newline at end of file diff --git a/src/file_deduplication.py b/src/utils/file_deduplication.py similarity index 100% rename from src/file_deduplication.py rename to src/utils/file_deduplication.py