diff --git a/debug_tools/test_progress.py b/debug_tools/test_progress.py new file mode 100644 index 0000000..e2fe9e6 --- /dev/null +++ b/debug_tools/test_progress.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Test script to verify progress callback is working +""" +import sys +import os + +sys.path.insert(0, "src") + +from adb_manager import ADBManager +import time + + +def test_progress_callback(): + """Test the progress callback functionality.""" + print("Testing progress callback...") + + # Track progress updates + progress_updates = [] + + def progress_callback(percentage): + progress_updates.append(percentage) + print(f"Progress update: {percentage}%") + + def status_callback(message): + print(f"Status: {message}") + + # Create ADB manager and set callbacks + adb = ADBManager() + adb.set_progress_callback(progress_callback) + adb.set_status_callback(status_callback) + + # Test manual progress updates + print("\nTesting manual progress updates:") + for i in range(0, 101, 25): + adb._update_progress(i) + time.sleep(0.1) + + print(f"\nProgress updates received: {progress_updates}") + print(f"Total updates: {len(progress_updates)}") + + # Test progress parsing + print("\nTesting progress parsing:") + test_lines = [ + "Pulling: /sdcard/DCIM/Camera/IMG_20250815_123456.jpg... (25%)", + "/sdcard/Documents/file.txt: 1 file pulled. (50%)", + "Pulling: /sdcard/Music/song.mp3... (75%)", + "3 files pulled. (100%)", + "No progress info here", + "", + ] + + for line in test_lines: + progress = adb.parse_progress(line) + print(f"Line: {repr(line[:50])} -> Progress: {progress}") + + +if __name__ == "__main__": + test_progress_callback() diff --git a/debug_tools/test_successive_transfers.py b/debug_tools/test_successive_transfers.py new file mode 100644 index 0000000..a60afcc --- /dev/null +++ b/debug_tools/test_successive_transfers.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +Debug successive transfers to understand progress bar issue +""" +import sys +import os +import time + +sys.path.insert(0, "src") + +from adb_manager import ADBManager + + +def test_successive_transfers(): + """Test multiple transfers to debug progress issues.""" + print("Testing successive transfer callbacks...") + + # Track progress updates for each transfer + transfer1_progress = [] + transfer2_progress = [] + current_transfer = 1 + + def progress_callback(percentage): + if current_transfer == 1: + transfer1_progress.append(percentage) + print(f"Transfer 1 - Progress: {percentage}%") + else: + transfer2_progress.append(percentage) + print(f"Transfer 2 - Progress: {percentage}%") + + def status_callback(message): + print(f"Transfer {current_transfer} - Status: {message}") + + # Create ADB manager and set callbacks + adb = ADBManager() + adb.set_progress_callback(progress_callback) + adb.set_status_callback(status_callback) + + # Test first transfer simulation + print("\n=== FIRST TRANSFER ===") + current_transfer = 1 + print("Simulating first transfer...") + adb._update_progress(0) + time.sleep(0.5) + adb._update_progress(25) + time.sleep(0.5) + adb._update_progress(50) + time.sleep(0.5) + adb._update_progress(75) + time.sleep(0.5) + adb._update_progress(100) + + print(f"First transfer progress updates: {transfer1_progress}") + + # Test second transfer simulation + print("\n=== SECOND TRANSFER ===") + current_transfer = 2 + print("Simulating second transfer...") + adb._update_progress(0) + time.sleep(0.5) + adb._update_progress(30) + time.sleep(0.5) + adb._update_progress(60) + time.sleep(0.5) + adb._update_progress(90) + time.sleep(0.5) + adb._update_progress(100) + + print(f"Second transfer progress updates: {transfer2_progress}") + + # Check if callbacks are still working + print(f"\nCallback function still set: {adb.progress_callback is not None}") + print(f"Status callback still set: {adb.status_callback is not None}") + + +if __name__ == "__main__": + test_successive_transfers() diff --git a/src/adb_manager.py b/src/adb_manager.py index 4222203..0d53835 100644 --- a/src/adb_manager.py +++ b/src/adb_manager.py @@ -157,6 +157,10 @@ class ADBManager: cmd = [ADB_BINARY_PATH, "pull", remote_path, local_path] try: + # Start with initial progress + self._update_progress(0) + self._update_status("Starting transfer...") + proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, @@ -165,11 +169,22 @@ class ADBManager: bufsize=1, ) + line_count = 0 + last_progress = 0 if proc.stdout: for line in proc.stdout: + line_count += 1 pct = self.parse_progress(line) if pct is not None: self._update_progress(pct) + last_progress = pct + else: + # If no explicit progress, simulate some progress based on activity + if line_count % 5 == 0 and last_progress < 90: + estimated_progress = min(last_progress + 5, 90) + self._update_progress(estimated_progress) + last_progress = estimated_progress + self._update_status(line.strip()) proc.wait() @@ -190,6 +205,10 @@ class ADBManager: cmd = [ADB_BINARY_PATH, "push", local_path, remote_path] try: + # Start with initial progress + self._update_progress(0) + self._update_status("Starting transfer...") + proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, @@ -201,13 +220,22 @@ class ADBManager: self._update_status(f"Failed to start adb: {e}") return False - self._update_status("Transferring files...") - + line_count = 0 + last_progress = 0 if proc.stdout: for line in proc.stdout: + line_count += 1 pct = self.parse_progress(line) if pct is not None: self._update_progress(pct) + last_progress = pct + else: + # If no explicit progress, simulate some progress based on activity + if line_count % 5 == 0 and last_progress < 90: + estimated_progress = min(last_progress + 5, 90) + self._update_progress(estimated_progress) + last_progress = estimated_progress + self._update_status(line.strip()) proc.wait() diff --git a/src/gui.py b/src/gui.py index 6c12858..c0c5750 100644 --- a/src/gui.py +++ b/src/gui.py @@ -39,6 +39,9 @@ class AndroidFileHandlerGUI(tk.Tk): self.adb_manager.set_progress_callback(self.update_progress) self.adb_manager.set_status_callback(self.set_status) + # Transfer tracking for thread safety + self.current_transfer_id = 0 + if get_platform_type().startswith("linux"): self.mtp_manager = LinuxMTPManager() else: @@ -590,7 +593,12 @@ class AndroidFileHandlerGUI(tk.Tk): self.start_btn.config(state="disabled") def enable_controls(self): - """Enable UI controls after operations.""" + """Enable UI controls after operations (thread-safe).""" + # Schedule UI update on main thread + self.after(0, self._enable_controls_ui) + + def _enable_controls_ui(self): + """Internal method to enable controls on main thread.""" self.remote_path_entry.config(state="normal") self.local_path_entry.config(state="normal") self.start_btn.config(state="normal") @@ -609,7 +617,12 @@ class AndroidFileHandlerGUI(tk.Tk): messagebox.showinfo("Enable USB Debugging", msg) def show_disable_debugging_reminder(self): - """Show reminder to disable USB debugging after transfer.""" + """Show reminder to disable USB debugging after transfer (thread-safe).""" + # Schedule UI update on main thread + self.after(0, self._show_debugging_reminder_ui) + + def _show_debugging_reminder_ui(self): + """Internal method to show debugging reminder on main thread.""" msg = ( "Transfer completed.\n\n" "For security, disable USB debugging when done:\n" @@ -634,57 +647,100 @@ class AndroidFileHandlerGUI(tk.Tk): return # Start transfer + self.current_transfer_id += 1 + transfer_id = self.current_transfer_id + self.disable_controls() - self.progress["value"] = 0 + # Reset progress bar in thread-safe way + self._update_progress_ui(0) if direction == "pull": self.set_status("Starting pull transfer...") threading.Thread( - target=self._pull_thread, args=(remote_path, local_path), daemon=True + target=self._pull_thread, + args=(remote_path, local_path, transfer_id), + daemon=True, ).start() elif direction == "push": self.set_status("Starting push transfer...") threading.Thread( - target=self._push_thread, args=(local_path, remote_path), daemon=True + target=self._push_thread, + args=(local_path, remote_path, transfer_id), + daemon=True, ).start() else: self.report_error("Invalid transfer direction selected.") self.enable_controls() - def _pull_thread(self, remote_path: str, local_path: str): + def _pull_thread(self, remote_path: str, local_path: str, transfer_id: int): """Thread function for pull operations.""" try: + # Check if this transfer is still current + if self.current_transfer_id != transfer_id: + return + success = self.adb_manager.pull_folder(remote_path, local_path) - if success: + if success and self.current_transfer_id == transfer_id: self.show_disable_debugging_reminder() except Exception as e: - self.report_error(f"Pull operation failed: {e}") + if self.current_transfer_id == transfer_id: + self.report_error(f"Pull operation failed: {e}") finally: - self.enable_controls() + if self.current_transfer_id == transfer_id: + self.enable_controls() - def _push_thread(self, local_path: str, remote_path: str): + def _push_thread(self, local_path: str, remote_path: str, transfer_id: int): """Thread function for push operations.""" try: + # Check if this transfer is still current + if self.current_transfer_id != transfer_id: + return + success = self.adb_manager.push_folder(local_path, remote_path) - if success: + if success and self.current_transfer_id == transfer_id: self.show_disable_debugging_reminder() except Exception as e: - self.report_error(f"Push operation failed: {e}") + if self.current_transfer_id == transfer_id: + self.report_error(f"Push operation failed: {e}") finally: - self.enable_controls() + if self.current_transfer_id == transfer_id: + self.enable_controls() def update_progress(self, percentage: int): - """Update the progress bar.""" + """Update the progress bar (thread-safe).""" + + # Schedule UI update on main thread using a proper closure + def update_ui(): + self._update_progress_ui(percentage) + + self.after(0, update_ui) + + def _update_progress_ui(self, percentage: int): + """Internal method to update progress bar on main thread.""" self.progress["value"] = percentage self.update_idletasks() def set_status(self, message: str): - """Update the status label.""" + """Update the status label (thread-safe).""" + + # Schedule UI update on main thread using a proper closure + def update_ui(): + self._set_status_ui(message) + + self.after(0, update_ui) + + def _set_status_ui(self, message: str): + """Internal method to update status on main thread.""" self.status_label.config(text=f"Status: {message}") self.update_idletasks() def report_error(self, message: str): - """Report an error to the user.""" + """Report an error to the user (thread-safe).""" + # Schedule UI update on main thread + self.after(0, lambda: self._report_error_ui(message)) + + def _report_error_ui(self, message: str): + """Internal method to report error on main thread.""" self.status_label.config(text=f"Error: {message}") messagebox.showerror("Error", message) self.enable_controls()