From 21e858dfca9dc0ccdde5adc4521c3488971381b3 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Tue, 9 Sep 2025 03:54:28 -0500 Subject: [PATCH] WIP progress count for files added and text indicating the file deduplication stage --- src/adb_manager.py | 108 +++++++++++++++++++++++++++++++++++++-- src/gui/main_window.py | 111 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 208 insertions(+), 11 deletions(-) diff --git a/src/adb_manager.py b/src/adb_manager.py index a5aaef8..5e0be2a 100644 --- a/src/adb_manager.py +++ b/src/adb_manager.py @@ -18,7 +18,10 @@ import tempfile from typing import Optional import hashlib -from file_deduplication import FileDeduplicator +try: + from file_deduplication import FileDeduplicator +except ImportError: + from .file_deduplication import FileDeduplicator def get_executable_directory() -> str: """Get the directory containing the executable or script.""" @@ -244,6 +247,12 @@ class ADBManager: status_callback=self._update_status, progress_callback=self._update_progress ) + # Transfer progress tracking + self.transfer_progress = { + 'current_file': 0, + 'total_files': 0, + 'files_to_transfer': 0 + } def set_progress_callback(self, callback: Callable[[int], None]): """Set callback function for progress updates.""" @@ -263,6 +272,23 @@ class ADBManager: if self.status_callback: self.status_callback(message) + def _update_transfer_progress(self, current_file: int, total_files: int): + """Update transfer progress for file counting.""" + self.transfer_progress['current_file'] = current_file + self.transfer_progress['total_files'] = total_files + # Send progress update through status callback with special format + progress_message = f"TRANSFER_PROGRESS:{current_file}:{total_files}" + if self.status_callback: + self.status_callback(progress_message) + + def _reset_transfer_progress(self): + """Reset transfer progress counters.""" + self.transfer_progress = { + 'current_file': 0, + 'total_files': 0, + 'files_to_transfer': 0 + } + def check_local_disk_space(self) -> bool: """Check if there's enough disk space for ADB download.""" try: @@ -384,6 +410,9 @@ class ADBManager: "Warning: Transferring to root drive. Consider using a subfolder." ) + # Reset transfer progress + self._reset_transfer_progress() + cmd = [get_adb_binary_path(), "pull", remote_path, local_path] self._update_status(f"Command: adb pull '{remote_path}' '{local_path}'") @@ -404,11 +433,29 @@ class ADBManager: last_progress = 0 start_time = time.time() last_update_time = start_time + files_transferred = 0 if proc.stdout: for line in proc.stdout: line_count += 1 current_time = time.time() + + # Check for file completion patterns in ADB output + if ": 1 file pulled" in line or "files pulled" in line: + # Extract number of files from the line + if "1 file pulled" in line: + files_transferred += 1 + else: + # Parse "X files pulled" pattern + import re + match = re.search(r'(\d+) files pulled', line) + if match: + files_transferred = int(match.group(1)) + + # Update file transfer progress + if self.transfer_progress['files_to_transfer'] > 0: + self._update_transfer_progress(files_transferred, self.transfer_progress['files_to_transfer']) + pct = self.parse_progress(line) if pct is not None: @@ -482,6 +529,9 @@ class ADBManager: "Warning: Pushing from root drive. Consider using a subfolder." ) + # Reset transfer progress + self._reset_transfer_progress() + cmd = [get_adb_binary_path(), "push", local_path, remote_path] self._update_status(f"Command: adb push '{local_path}' '{remote_path}'") @@ -505,11 +555,29 @@ class ADBManager: last_progress = 0 start_time = time.time() last_update_time = start_time + files_transferred = 0 if proc.stdout: for line in proc.stdout: line_count += 1 current_time = time.time() + + # Check for file completion patterns in ADB output + if ": 1 file pushed" in line or "files pushed" in line: + # Extract number of files from the line + if "1 file pushed" in line: + files_transferred += 1 + else: + # Parse "X files pushed" pattern + import re + match = re.search(r'(\d+) files pushed', line) + if match: + files_transferred = int(match.group(1)) + + # Update file transfer progress + if self.transfer_progress['files_to_transfer'] > 0: + self._update_transfer_progress(files_transferred, self.transfer_progress['files_to_transfer']) + pct = self.parse_progress(line) if pct is not None: @@ -571,6 +639,10 @@ class ADBManager: "Warning: Transferring to root drive. Consider using a subfolder." ) + # Set file count for single file transfer + self.transfer_progress['files_to_transfer'] = 1 + self._update_transfer_progress(0, 1) + cmd = [get_adb_binary_path(), "pull", remote_file_path, local_file_path] # Debug output for troubleshooting @@ -599,6 +671,11 @@ class ADBManager: for line in proc.stdout: line_count += 1 current_time = time.time() + + # Check for file completion + if "1 file pulled" in line: + self._update_transfer_progress(1, 1) + pct = self.parse_progress(line) if pct is not None: @@ -665,6 +742,10 @@ class ADBManager: "Warning: Pushing from root drive. Consider using a subfolder." ) + # Set file count for single file transfer + self.transfer_progress['files_to_transfer'] = 1 + self._update_transfer_progress(0, 1) + cmd = [get_adb_binary_path(), "push", local_file_path, remote_file_path] # Debug output for troubleshooting @@ -696,6 +777,11 @@ class ADBManager: for line in proc.stdout: line_count += 1 current_time = time.time() + + # Check for file completion + if "1 file pushed" in line: + self._update_transfer_progress(1, 1) + pct = self.parse_progress(line) if pct is not None: @@ -784,6 +870,9 @@ class ADBManager: # Create local directory os.makedirs(local_path, exist_ok=True) + # Start duplicate scanning + self._update_status("Scanning for duplicates...") + # Get list of remote files stdout, stderr, returncode = self.run_adb_command(['shell', 'find', remote_path, '-type', 'f'], capture_output=True) @@ -820,10 +909,15 @@ class ADBManager: duplicate_files, is_remote=True, adb_command_runner=self.run_adb_command ) stats['bytes_saved'] = bytes_saved - self._update_status(f"Skipping {files_saved} duplicates, saving {self.deduplicator.format_bytes(bytes_saved)}") + self._update_status(f"Duplicate scan complete. Skipping {files_saved} duplicates, saving {self.deduplicator.format_bytes(bytes_saved)}") + else: + self._update_status("Duplicate scan complete. No duplicates found.") # Transfer non-duplicate files if files_to_transfer: + # Set the file count for transfer progress tracking + self.transfer_progress['files_to_transfer'] = len(files_to_transfer) + self._update_status("Starting transfer...") success = self.pull_folder(remote_path, local_path) if success: stats['transferred'] = len(files_to_transfer) @@ -862,6 +956,9 @@ class ADBManager: self._update_status(f"Local path does not exist: {local_path}") return False, stats + # Start duplicate scanning + self._update_status("Scanning for duplicates...") + # Get list of local files local_files = [] for root, dirs, files in os.walk(local_path): @@ -897,10 +994,15 @@ class ADBManager: duplicate_files, is_remote=False ) stats['bytes_saved'] = bytes_saved - self._update_status(f"Skipping {files_saved} duplicates, saving {self.deduplicator.format_bytes(bytes_saved)}") + self._update_status(f"Duplicate scan complete. Skipping {files_saved} duplicates, saving {self.deduplicator.format_bytes(bytes_saved)}") + else: + self._update_status("Duplicate scan complete. No duplicates found.") # Transfer non-duplicate files if files_to_transfer: + # Set the file count for transfer progress tracking + self.transfer_progress['files_to_transfer'] = len(files_to_transfer) + self._update_status("Starting transfer...") success = self.push_folder(local_path, remote_path) if success: stats['transferred'] = len(files_to_transfer) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 070738f..1d9ebbd 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -237,6 +237,13 @@ class AndroidFileHandlerGUI(tk.Tk): self.transfer_animation_job = None self.transfer_dots = 0 + # File transfer progress tracking + self.transfer_file_progress = { + 'current': 0, + 'total': 0, + 'active': False + } + # Set up ADB callbacks to our own methods self.adb_manager.set_progress_callback(self._update_progress) self.adb_manager.set_status_callback(self._update_status) @@ -253,11 +260,50 @@ class AndroidFileHandlerGUI(tk.Tk): def _update_status(self, message: str): """Update status label (thread-safe).""" - # Don't update status if transfer animation is running - if self.transfer_animation_job is not None: - return # Ignore ADB status updates during transfer animation - def update_ui(): + # Check for transfer progress updates + if message.startswith("TRANSFER_PROGRESS:"): + parts = message.split(":") + if len(parts) == 3: + current = int(parts[1]) + total = int(parts[2]) + self.transfer_file_progress['current'] = current + self.transfer_file_progress['total'] = total + self.transfer_file_progress['active'] = True + # Don't update the UI here - let the animation handle it + return + + # Check if we should start or switch animations based on message content + if self.transfer_animation_job is not None: + # Animation is already running + if "Scanning for duplicates" in message and not hasattr(self, '_scanning_active'): + # Already scanning, ignore duplicate "Scanning for duplicates..." messages + return + elif "Starting transfer" in message or "Transferring" in message: + # Switch from scanning to transfer animation + if hasattr(self, '_scanning_active'): + delattr(self, '_scanning_active') + self._stop_transfer_animation() + self._start_transfer_animation() + return + elif ("Duplicate scan complete" in message or + "No duplicates found" in message or + "All files already exist" in message): + # Allow these messages to show briefly before transfer starts + self._stop_transfer_animation() + self.status_label.config(text=message) + self.update_idletasks() + return + else: + # Don't update status if transfer animation is running (except for specific cases) + return + elif "Scanning for duplicates" in message: + # Start scanning animation + self._scanning_active = True + self._start_scanning_animation() + return + + # Normal status update self.status_label.config(text=message) self.update_idletasks() @@ -313,11 +359,37 @@ class AndroidFileHandlerGUI(tk.Tk): self.transfer_animation_job = True # Mark as active before starting self._animate_transfer_text() + def _start_scanning_animation(self): + """Start the 'Scanning for duplicates...' animation.""" + self.transfer_dots = 0 + self.transfer_animation_job = True # Mark as active before starting + self._animate_scanning_text() + + def _animate_scanning_text(self): + """Animate the scanning text with dots.""" + if self.transfer_animation_job is not None: + dots = "." * (self.transfer_dots + 1) + status_text = f"Scanning for duplicates{dots}" + self.status_label.config(text=status_text) + self.update_idletasks() # Force immediate UI update + self.transfer_dots = (self.transfer_dots + 1) % 5 # Cycle 0-4 dots + # Schedule next update in 500ms + self.transfer_animation_job = self.after(500, self._animate_scanning_text) + def _animate_transfer_text(self): """Animate the transfer text with dots.""" if self.transfer_animation_job is not None: dots = "." * (self.transfer_dots + 1) - status_text = f"Transferring{dots}" + + # Show file progress if available + if (self.transfer_file_progress['active'] and + self.transfer_file_progress['total'] > 0): + current = self.transfer_file_progress['current'] + total = self.transfer_file_progress['total'] + status_text = f"Transferring {current} of {total} files{dots}" + else: + status_text = f"Transferring{dots}" + self.status_label.config(text=status_text) self.update_idletasks() # Force immediate UI update self.transfer_dots = (self.transfer_dots + 1) % 5 # Cycle 0-4 dots @@ -330,6 +402,17 @@ class AndroidFileHandlerGUI(tk.Tk): if isinstance(self.transfer_animation_job, str): # It's an after job ID self.after_cancel(self.transfer_animation_job) self.transfer_animation_job = None + + # Clear scanning flag if it exists + if hasattr(self, '_scanning_active'): + delattr(self, '_scanning_active') + + # Reset file progress tracking + self.transfer_file_progress = { + 'current': 0, + 'total': 0, + 'active': False + } def _initialize_app(self): """Initialize the application - check ADB and device.""" @@ -781,13 +864,19 @@ class AndroidFileHandlerGUI(tk.Tk): self.disable_controls() # Switch button to cancel mode during transfer self._switch_to_cancel_mode() - # Start the transfer animation - self._start_transfer_animation() - + # Start with scanning animation for folder transfers + # File transfers will skip directly to transfer animation + # Determine if we're dealing with files or folders if direction == "pull": # For pull, check if remote path is a file is_file = self._is_remote_file(remote_path) + if not is_file: + # Start scanning animation for folder transfers + self._start_scanning_animation() + else: + # Start transfer animation for file transfers + self._start_transfer_animation() threading.Thread( target=self._transfer_thread, args=(direction, remote_path, local_path, transfer_id, is_file), @@ -796,6 +885,12 @@ class AndroidFileHandlerGUI(tk.Tk): elif direction == "push": # For push, check if local path is a file is_file = os.path.isfile(local_path) + if not is_file: + # Start scanning animation for folder transfers + self._start_scanning_animation() + else: + # Start transfer animation for file transfers + self._start_transfer_animation() threading.Thread( target=self._transfer_thread, args=(direction, local_path, remote_path, transfer_id, is_file),