Add select and fcntl modules to PyInstaller hiddenimports

- Added 'select' and 'fcntl' modules to hiddenimports in build_autoinstaller_gui.py
- These modules are used for non-blocking I/O operations in the copy_live_system method
- Ensures PyInstaller includes these standard library modules in the frozen binary
- Prevents import errors when running the frozen AutoInstaller GUI binary
- Required for dynamic progress updates during system copy process

This ensures the PyInstaller frozen binary has all necessary modules
for the enhanced copy progress functionality with non-blocking I/O.
parent ec3319e2
...@@ -10,6 +10,8 @@ import subprocess ...@@ -10,6 +10,8 @@ import subprocess
import threading import threading
import re import re
import time import time
import select
import fcntl
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
import glob import glob
...@@ -417,24 +419,24 @@ class InstallerWorker(QThread): ...@@ -417,24 +419,24 @@ class InstallerWorker(QThread):
self.status_updated.emit("Detecting devices...") self.status_updated.emit("Detecting devices...")
self.progress_updated.emit(10) self.progress_updated.emit(10)
# Step 1: Device Detection # Step 1: Device Detection (5%)
usb_device = self.detect_usb_device() usb_device = self.detect_usb_device()
target_disk = self.detect_target_disk(usb_device) target_disk = self.detect_target_disk(usb_device)
self.config['usb_device'] = usb_device self.config['usb_device'] = usb_device
self.config['target_disk'] = target_disk self.config['target_disk'] = target_disk
self.step_completed.emit("Device detection completed") self.step_completed.emit("Device detection completed")
self.progress_updated.emit(20) self.progress_updated.emit(5)
# Step 2: Timezone (use config or default) # Step 2: Timezone (10%)
timezone = self.config.get('timezone', 'UTC') timezone = self.config.get('timezone', 'UTC')
self.log(f"Setting timezone to {timezone}") self.log(f"Setting timezone to {timezone}")
self.status_updated.emit(f"Setting timezone: {timezone}") self.status_updated.emit(f"Setting timezone: {timezone}")
self.step_completed.emit("Timezone set") self.step_completed.emit("Timezone set")
self.progress_updated.emit(30) self.progress_updated.emit(10)
# Step 3: Network Configuration (moved before mounting) # Step 3: Network Configuration (15%)
if self.config.get('configure_network', False): if self.config.get('configure_network', False):
self.status_updated.emit("Preparing network configuration...") self.status_updated.emit("Preparing network configuration...")
# Just store the config for now, will apply after mounting # Just store the config for now, will apply after mounting
...@@ -443,55 +445,55 @@ class InstallerWorker(QThread): ...@@ -443,55 +445,55 @@ class InstallerWorker(QThread):
else: else:
self.log("Skipping network configuration") self.log("Skipping network configuration")
self.progress_updated.emit(40) self.progress_updated.emit(15)
# Step 4: Find Preseed # Step 4: Find Preseed (20%)
preseed_file = self.find_preseed() preseed_file = self.find_preseed()
self.config['preseed_file'] = preseed_file self.config['preseed_file'] = preseed_file
self.step_completed.emit("Configuration loaded") self.step_completed.emit("Configuration loaded")
self.progress_updated.emit(50) self.progress_updated.emit(20)
# Step 5: Confirmation (already handled in GUI) # Step 5: Confirmation (25%)
self.status_updated.emit("User confirmed installation") self.status_updated.emit("User confirmed installation")
# Step 6: Partition Disk # Step 6: Partition Disk (30%)
self.status_updated.emit("Partitioning target disk...") self.status_updated.emit("Partitioning target disk...")
self.partition_disk(target_disk) self.partition_disk(target_disk)
self.step_completed.emit("Disk partitioned") self.step_completed.emit("Disk partitioned")
self.progress_updated.emit(60) self.progress_updated.emit(30)
# Step 7: Mount Target # Step 7: Mount Target (35%)
self.status_updated.emit("Mounting target filesystem...") self.status_updated.emit("Mounting target filesystem...")
target_mount = "/target" target_mount = "/target"
self.mount_target(target_disk, target_mount) self.mount_target(target_disk, target_mount)
self.step_completed.emit("Target mounted") self.step_completed.emit("Target mounted")
self.progress_updated.emit(65) self.progress_updated.emit(35)
# Step 8: Copy Live System # Step 8: Copy Live System (85%) - Major step, 50% of total progress
self.status_updated.emit("Copying live system to disk...") self.status_updated.emit("Copying live system to disk...")
self.copy_live_system(target_mount) self.copy_live_system(target_mount)
self.step_completed.emit("System copied") self.step_completed.emit("System copied")
self.progress_updated.emit(75) self.progress_updated.emit(85)
# Step 9: Configure Target System (includes network configuration) # Step 9: Configure Target System (90%)
self.status_updated.emit("Configuring target system...") self.status_updated.emit("Configuring target system...")
self.configure_target_system(target_mount) self.configure_target_system(target_mount)
self.step_completed.emit("System configured") self.step_completed.emit("System configured")
self.progress_updated.emit(95) self.progress_updated.emit(90)
# Step 12: Install Bootloader # Step 10: Install Bootloader (95%)
self.status_updated.emit("Installing bootloader...") self.status_updated.emit("Installing bootloader...")
self.install_bootloader(target_mount, target_disk) self.install_bootloader(target_mount, target_disk)
self.step_completed.emit("Bootloader installed") self.step_completed.emit("Bootloader installed")
self.progress_updated.emit(98) self.progress_updated.emit(95)
# Post-Install Setup # Step 11: Post-Install Setup (100%)
self.status_updated.emit("Running post-installation setup...") self.status_updated.emit("Running post-installation setup...")
self.run_post_install(target_mount) self.run_post_install(target_mount)
self.step_completed.emit("Post-install complete") self.step_completed.emit("Post-install complete")
...@@ -719,12 +721,12 @@ class InstallerWorker(QThread): ...@@ -719,12 +721,12 @@ class InstallerWorker(QThread):
self.log("Target mounted") self.log("Target mounted")
def copy_live_system(self, target_mount): def copy_live_system(self, target_mount):
"""Copy live system with detailed progress output""" """Copy live system with dynamic progress updates"""
self.log("Starting system copy with detailed progress...") self.log("Starting system copy with dynamic progress...")
# Use rsync with progress and verbose output # Use rsync with progress output for dynamic updates
cmd = [ cmd = [
'rsync', '-av', '--progress', '--stats', 'rsync', '-av', '--progress', '--stats', '--human-readable',
'--exclude=/proc', '--exclude=/sys', '--exclude=/dev', '--exclude=/proc', '--exclude=/sys', '--exclude=/dev',
'--exclude=/tmp', '--exclude=/run', '--exclude=/mnt', '--exclude=/tmp', '--exclude=/run', '--exclude=/mnt',
'--exclude=/media', '--exclude=/lost+found', '--exclude=/media', '--exclude=/lost+found',
...@@ -745,17 +747,15 @@ class InstallerWorker(QThread): ...@@ -745,17 +747,15 @@ class InstallerWorker(QThread):
universal_newlines=True universal_newlines=True
) )
# Read output line by line and send to GUI (non-blocking) # Progress tracking variables
import select copy_progress_start = 35 # Start at 35%
import fcntl copy_progress_end = 85 # End at 85%
import os copy_progress_range = copy_progress_end - copy_progress_start
# Make stdout non-blocking
fd = process.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
last_update_time = 0 last_update_time = 0
total_files = 0
processed_files = 0
while True: while True:
# Check if process is still running # Check if process is still running
if process.poll() is not None: if process.poll() is not None:
...@@ -777,8 +777,37 @@ class InstallerWorker(QThread): ...@@ -777,8 +777,37 @@ class InstallerWorker(QThread):
if ready: if ready:
output = process.stdout.readline() output = process.stdout.readline()
if output and output.strip(): if output and output.strip():
self.copy_progress.emit(output.strip()) line = output.strip()
self.copy_progress.emit(line)
# Parse rsync progress for dynamic updates
# Look for lines like: "12,345 100% 123.45MB/s 0:12:34 (xfr#1234, to-chk=5678/9012)"
if '%' in line and ('xfr#' in line or 'to-chk=' in line):
try:
# Extract progress information
parts = line.split()
for part in parts:
if 'xfr#' in part:
# Extract transferred files count
xfr_part = part.split('xfr#')[1].split(',')[0]
processed_files = int(xfr_part) if xfr_part.isdigit() else processed_files
elif 'to-chk=' in part:
# Extract total files count
chk_part = part.split('to-chk=')[1].split('/')[1] if '/' in part else '0'
total_files = int(chk_part) if chk_part.isdigit() else total_files
# Calculate progress percentage within copy range
if total_files > 0:
file_progress = processed_files / total_files
current_progress = copy_progress_start + (file_progress * copy_progress_range)
current_progress = min(current_progress, copy_progress_end)
self.progress_updated.emit(int(current_progress))
except (ValueError, IndexError):
pass
last_update_time = time.time() last_update_time = time.time()
except: except:
# Handle non-blocking read errors gracefully # Handle non-blocking read errors gracefully
pass pass
...@@ -796,6 +825,8 @@ class InstallerWorker(QThread): ...@@ -796,6 +825,8 @@ class InstallerWorker(QThread):
if process.returncode != 0: if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, cmd) raise subprocess.CalledProcessError(process.returncode, cmd)
# Ensure we reach the end of copy progress range
self.progress_updated.emit(copy_progress_end)
self.log("System copy completed successfully") self.log("System copy completed successfully")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
......
...@@ -23,7 +23,7 @@ a = Analysis( ...@@ -23,7 +23,7 @@ a = Analysis(
pathex=['.'], pathex=['.'],
binaries=[], binaries=[],
datas=[], datas=[],
hiddenimports=['PyQt6.QtCore', 'PyQt6.QtGui', 'PyQt6.QtWidgets', 'PyQt6.QtGui.QShortcut', 'PyQt6.QtGui.QKeySequence', 'subprocess', 'threading'], hiddenimports=['PyQt6.QtCore', 'PyQt6.QtGui', 'PyQt6.QtWidgets', 'PyQt6.QtGui.QShortcut', 'PyQt6.QtGui.QKeySequence', 'subprocess', 'threading', 'select', 'fcntl'],
hookspath=[], hookspath=[],
hooksconfig={{}}, hooksconfig={{}},
runtime_hooks=[], runtime_hooks=[],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment