735 lines
24 KiB
Python
Executable File
735 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# tsk/prefetch.py
|
|
"""
|
|
Prefetch Script for TSK Manager
|
|
|
|
This script runs before the main TSK Manager to prefetch necessary repositories.
|
|
It creates a GUI window with progress bars to track git clone operations for two repositories:
|
|
1. The recommended openpilot repository
|
|
2. The alternate openpilot repository
|
|
|
|
Features:
|
|
- Visual progress tracking with progress bars
|
|
- Directory cleanup before cloning
|
|
- Per-operation retry mechanism (up to 5 retries per operation)
|
|
- Automatic exit when operations complete or max retries reached
|
|
"""
|
|
|
|
# Standard library imports
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
from typing import List
|
|
|
|
# Third-party imports
|
|
import pyray as rl
|
|
|
|
# Local imports
|
|
from openpilot.system.ui.lib.application import gui_app
|
|
from tsk.common.env import (
|
|
RECOMMENDED_OP_USER,
|
|
RECOMMENDED_OP_BRANCH,
|
|
RECOMMENDED_OP_DIR,
|
|
ALTERNATE_OP_USER,
|
|
ALTERNATE_OP_BRANCH,
|
|
ALTERNATE_OP_DIR
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Configuration Constants
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Retry settings
|
|
MAX_RETRIES = 10 # Maximum number of retry attempts per operation
|
|
RETRY_DELAY = 10 # Seconds to wait between retry attempts
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Git Clone Progress Tracker
|
|
# -------------------------------------------------------------------------
|
|
|
|
class GitCloneProgress:
|
|
"""
|
|
Tracks the progress of a git clone operation.
|
|
|
|
This class handles running a git clone command in a separate thread,
|
|
parsing its output to track progress, and managing retries if the
|
|
operation fails.
|
|
|
|
Each instance represents one git clone operation with its own progress
|
|
tracking and retry mechanism.
|
|
"""
|
|
|
|
def __init__(self, command: List[str], title: str, target_dir: str = None):
|
|
"""
|
|
Initialize a git clone progress tracker.
|
|
|
|
Args:
|
|
command: The git clone command as a list of strings.
|
|
title: The title to display for this clone operation.
|
|
target_dir: The target directory to delete before cloning (if provided).
|
|
"""
|
|
# Command and identification
|
|
self.command = command
|
|
self.title = title
|
|
self.target_dir = target_dir
|
|
|
|
# Progress and status tracking
|
|
self.progress = 0
|
|
self.status = "Initializing..."
|
|
self.completed = False
|
|
self.failed = False
|
|
|
|
# Process and thread management
|
|
self.process = None
|
|
self.thread = None
|
|
|
|
# Retry mechanism
|
|
self.retry_count = 0
|
|
self.retry_needed = False
|
|
self.retry_timer = 0
|
|
|
|
def start(self):
|
|
"""
|
|
Start the git clone process in a separate thread.
|
|
|
|
This allows the GUI to remain responsive while the clone operation
|
|
runs in the background.
|
|
"""
|
|
self.thread = threading.Thread(target=self._run_process)
|
|
self.thread.daemon = True # Thread will exit when main program exits
|
|
self.thread.start()
|
|
|
|
def _run_process(self):
|
|
"""
|
|
Run the git clone process and update progress.
|
|
|
|
This method:
|
|
1. Deletes the target directory if it exists
|
|
2. Starts the git clone process
|
|
3. Parses output to track progress
|
|
4. Updates status based on completion or failure
|
|
"""
|
|
try:
|
|
# Step 1: Delete target directory if it exists
|
|
if self.target_dir and os.path.exists(self.target_dir):
|
|
self.status = f"Deleting existing directory: {self.target_dir}"
|
|
try:
|
|
shutil.rmtree(self.target_dir)
|
|
self.status = f"Deleted directory: {self.target_dir}"
|
|
except Exception as e:
|
|
# If directory deletion fails, mark as failed and prepare for retry
|
|
self.status = f"Error deleting directory: {str(e)}"
|
|
self.failed = True
|
|
self.retry_needed = True
|
|
self.retry_timer = time.time()
|
|
return
|
|
|
|
# Step 2: Start the git clone process
|
|
self.status = "Starting clone operation..."
|
|
self.process = subprocess.Popen(
|
|
self.command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True,
|
|
bufsize=1
|
|
)
|
|
|
|
# Step 3: Parse output to track progress
|
|
for line in self.process.stdout:
|
|
self._parse_progress(line)
|
|
|
|
# Step 4: Check process completion status
|
|
self.process.wait()
|
|
if self.process.returncode == 0:
|
|
# Success case
|
|
self.progress = 100
|
|
self.status = "Done"
|
|
self.completed = True
|
|
else:
|
|
# Failure case - prepare for retry
|
|
self.status = f"Failed with code {self.process.returncode}"
|
|
self.failed = True
|
|
self.retry_needed = True
|
|
self.retry_timer = time.time()
|
|
except Exception as e:
|
|
# Handle any unexpected exceptions
|
|
self.status = f"Error: {str(e)}"
|
|
self.failed = True
|
|
self.retry_needed = True
|
|
self.retry_timer = time.time()
|
|
|
|
def _parse_progress(self, line: str):
|
|
"""
|
|
Parse git output to extract progress information.
|
|
|
|
Git clone outputs progress in two main phases:
|
|
1. "Receiving objects: x%" - Maps to 0-90% of our progress bar
|
|
2. "Resolving deltas: x%" - Maps to 90-100% of our progress bar
|
|
|
|
Args:
|
|
line: A line of output from the git clone process
|
|
"""
|
|
# Phase 1: Look for "Receiving objects: x%" pattern
|
|
receiving_match = re.search(r'Receiving objects:\s+(\d+)%', line)
|
|
if receiving_match:
|
|
# Direct mapping for receiving objects phase
|
|
self.progress = int(receiving_match.group(1))
|
|
self.status = line.strip()
|
|
return
|
|
|
|
# Phase 2: Look for "Resolving deltas: x%" pattern
|
|
resolving_match = re.search(r'Resolving deltas:\s+(\d+)%', line)
|
|
if resolving_match:
|
|
# Map resolving deltas from 0-100% to 90-100% of overall progress
|
|
delta_progress = int(resolving_match.group(1))
|
|
adjusted_progress = 90 + (delta_progress / 10)
|
|
# Keep progress at least at current level (never go backwards)
|
|
self.progress = max(self.progress, adjusted_progress)
|
|
self.status = line.strip()
|
|
return
|
|
|
|
# Update status with current operation for any other informative lines
|
|
if line.strip():
|
|
self.status = line.strip()
|
|
|
|
def reset(self):
|
|
"""
|
|
Reset the progress tracker for a retry.
|
|
|
|
This prepares the tracker for a fresh attempt after a failure.
|
|
"""
|
|
self.progress = 0
|
|
self.status = "Initializing retry..."
|
|
self.completed = False
|
|
self.failed = False
|
|
self.process = None
|
|
self.thread = None
|
|
self.retry_needed = False
|
|
|
|
def check_retry(self):
|
|
"""
|
|
Check if it's time to retry and handle the retry if needed.
|
|
|
|
This method:
|
|
1. Checks if a retry is needed and possible
|
|
2. Waits for the retry delay to elapse
|
|
3. Increments retry count and starts a new attempt
|
|
|
|
Returns:
|
|
bool: True if a retry was initiated, False otherwise
|
|
"""
|
|
if self.failed and self.retry_needed and self.retry_count < MAX_RETRIES:
|
|
# Check if retry delay has elapsed
|
|
if time.time() - self.retry_timer >= RETRY_DELAY:
|
|
self.retry_count += 1
|
|
self.reset()
|
|
self.start()
|
|
return True
|
|
return False
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Main Application
|
|
# -------------------------------------------------------------------------
|
|
|
|
class PrefetchAppC3:
|
|
"""
|
|
GUI application to track git clone operations for C3 (big screen).
|
|
|
|
This class creates a window with progress bars to visualize the status
|
|
of git clone operations. It handles:
|
|
1. Setting up and starting clone operations
|
|
2. Rendering the UI with progress bars and status text
|
|
3. Managing the application lifecycle
|
|
4. Coordinating retries for failed operations
|
|
"""
|
|
|
|
# UI Constants
|
|
PROGRESS_BAR_HEIGHT = 100
|
|
PROGRESS_BAR_WIDTH = 2000
|
|
PADDING = 20
|
|
FONT_SIZE = 80
|
|
TITLE_FONT_SIZE = 120
|
|
BAR_BG_COLOR = rl.Color(40, 40, 40, 255) # Dark gray for progress bar background
|
|
BAR_FG_COLOR = rl.Color(54, 77, 239, 255) # Blue for progress bar foreground
|
|
WHITE_TEXT_COLOR = rl.Color(255, 255, 255, 255) # White for most text
|
|
GRAY_TEXT_COLOR = rl.Color(100, 100, 100, 255) # Dimmer gray for status text
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initialize the prefetch application.
|
|
|
|
Sets up the clone operations but doesn't start them yet.
|
|
"""
|
|
self.initialize_operations()
|
|
|
|
def initialize_operations(self):
|
|
"""
|
|
Initialize or reset the clone operations.
|
|
|
|
Creates GitCloneProgress instances for each repository we need to clone,
|
|
but only if the target directories don't already exist. This handles all
|
|
three possibilities gracefully: none, one, or both directories may exist.
|
|
"""
|
|
self.clone_operations = []
|
|
|
|
# Check if the recommended openpilot directory exists
|
|
recommended_exists = os.path.exists(RECOMMENDED_OP_DIR)
|
|
# Check if the alternate openpilot directory exists
|
|
alternate_exists = os.path.exists(ALTERNATE_OP_DIR)
|
|
|
|
# Only create operation for recommended repository if directory doesn't exist
|
|
if not recommended_exists:
|
|
self.clone_operations.append(
|
|
GitCloneProgress(
|
|
["/usr/bin/git", "clone", "--progress",
|
|
f"https://github.com/{RECOMMENDED_OP_USER}/openpilot.git",
|
|
"-b", RECOMMENDED_OP_BRANCH, "--depth=1",
|
|
"--recurse-submodules", RECOMMENDED_OP_DIR],
|
|
f"{RECOMMENDED_OP_USER}/{RECOMMENDED_OP_BRANCH}",
|
|
RECOMMENDED_OP_DIR # Target directory to delete before cloning
|
|
)
|
|
)
|
|
|
|
# Only create operation for alternate repository if directory doesn't exist
|
|
if not alternate_exists:
|
|
self.clone_operations.append(
|
|
GitCloneProgress(
|
|
["/usr/bin/git", "clone", "--progress",
|
|
f"https://github.com/{ALTERNATE_OP_USER}/openpilot.git",
|
|
"-b", ALTERNATE_OP_BRANCH, "--depth=1",
|
|
"--recurse-submodules", ALTERNATE_OP_DIR],
|
|
f"{ALTERNATE_OP_USER}/{ALTERNATE_OP_BRANCH}",
|
|
ALTERNATE_OP_DIR # Target directory to delete before cloning
|
|
)
|
|
)
|
|
|
|
def run(self):
|
|
"""
|
|
Run the prefetch application with retry mechanism.
|
|
|
|
This method:
|
|
1. Initializes the window
|
|
2. Starts clone operations
|
|
3. Runs the main loop until completion or window close
|
|
4. Handles cleanup and exit
|
|
"""
|
|
# Step 1: Initialize window using gui_app (consistent with main.py)
|
|
gui_app.init_window("TSK Prefetch")
|
|
|
|
# Step 2: Start clone operations
|
|
self.start_operations()
|
|
|
|
# Step 3: Main loop
|
|
while not rl.window_should_close():
|
|
# Check for retries in each operation
|
|
for op in self.clone_operations:
|
|
op.check_retry()
|
|
|
|
# Check if all operations are complete or have reached max retries
|
|
all_done = True
|
|
for op in self.clone_operations:
|
|
if not op.completed and (not op.failed or op.retry_count < MAX_RETRIES):
|
|
all_done = False
|
|
break
|
|
|
|
# Exit condition - all operations are either complete or have reached max retries
|
|
if all_done:
|
|
time.sleep(1) # Show final state briefly
|
|
break
|
|
|
|
# Render the current frame
|
|
self._render_frame()
|
|
|
|
# Step 4: Cleanup and exit
|
|
rl.close_window()
|
|
|
|
def start_operations(self):
|
|
"""
|
|
Start all clone operations.
|
|
|
|
Initiates the background threads for all git clone operations.
|
|
"""
|
|
for op in self.clone_operations:
|
|
op.start()
|
|
|
|
def _render_frame(self):
|
|
"""
|
|
Render a single frame of the application.
|
|
|
|
This method:
|
|
1. Clears the background
|
|
2. Draws the title
|
|
3. For each operation:
|
|
a. Draws the operation title with retry info
|
|
b. Draws the progress bar
|
|
c. Draws the progress percentage
|
|
d. Draws the status text with retry countdown if applicable
|
|
"""
|
|
# Step 1: Begin drawing and clear background
|
|
# Use render texture if scaling is enabled (matches gui_app behavior)
|
|
if gui_app._render_texture:
|
|
rl.begin_texture_mode(gui_app._render_texture)
|
|
rl.clear_background(rl.Color(0, 0, 0, 255))
|
|
else:
|
|
rl.begin_drawing()
|
|
rl.clear_background(rl.Color(0, 0, 0, 255)) # Black background
|
|
|
|
# Step 2: Draw title
|
|
title = "Prefetching"
|
|
title_width = rl.measure_text_ex(gui_app.font(), title, self.TITLE_FONT_SIZE, 0).x
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
title,
|
|
rl.Vector2((gui_app.width - title_width) // 2, self.PADDING),
|
|
self.TITLE_FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
|
|
# Step 3: Draw progress bars for each operation
|
|
y_offset = self.PADDING * 3 + self.TITLE_FONT_SIZE * 2
|
|
|
|
for i, op in enumerate(self.clone_operations):
|
|
# Step 3a: Draw operation title with retry count if applicable
|
|
title_text = op.title
|
|
if op.retry_count > 0:
|
|
title_text += f" (Retry {op.retry_count}/{MAX_RETRIES})"
|
|
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
title_text,
|
|
rl.Vector2(self.PADDING, y_offset),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
y_offset += self.FONT_SIZE + self.PADDING
|
|
|
|
# Step 3b: Draw progress bar background
|
|
bar_x = (gui_app.width - self.PROGRESS_BAR_WIDTH) // 2
|
|
bar_rect = rl.Rectangle(bar_x, y_offset, self.PROGRESS_BAR_WIDTH, self.PROGRESS_BAR_HEIGHT)
|
|
rl.draw_rectangle_rec(bar_rect, self.BAR_BG_COLOR)
|
|
|
|
# Step 3c: Draw progress bar foreground
|
|
progress_width = (op.progress / 100.0) * self.PROGRESS_BAR_WIDTH
|
|
progress_rect = rl.Rectangle(bar_x, y_offset, progress_width, self.PROGRESS_BAR_HEIGHT)
|
|
rl.draw_rectangle_rec(progress_rect, self.BAR_FG_COLOR)
|
|
|
|
# Step 3d: Draw progress percentage
|
|
progress_text = f"{op.progress}%"
|
|
text_width = rl.measure_text_ex(gui_app.font(), progress_text, self.FONT_SIZE, 0).x
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
progress_text,
|
|
rl.Vector2(
|
|
bar_x + (self.PROGRESS_BAR_WIDTH - text_width) // 2,
|
|
y_offset + (self.PROGRESS_BAR_HEIGHT - self.FONT_SIZE) // 2
|
|
),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
|
|
# Step 3e: Draw status text with retry information if applicable
|
|
status_y = y_offset + self.PROGRESS_BAR_HEIGHT + self.PADDING
|
|
status_text = op.status
|
|
|
|
# Add retry countdown or max retries reached message if applicable
|
|
if op.failed and op.retry_needed and op.retry_count < MAX_RETRIES:
|
|
countdown = max(0, int(RETRY_DELAY - (time.time() - op.retry_timer)))
|
|
status_text += f" - Retrying in {countdown}s..."
|
|
elif op.failed and op.retry_count >= MAX_RETRIES:
|
|
status_text += f" - Max retries reached"
|
|
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
status_text,
|
|
rl.Vector2(self.PADDING, status_y),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.GRAY_TEXT_COLOR
|
|
)
|
|
|
|
# Update y_offset for next operation
|
|
y_offset += self.PROGRESS_BAR_HEIGHT + self.PADDING * 2 + self.FONT_SIZE * 2
|
|
|
|
# End drawing and scale if needed (matches gui_app behavior)
|
|
if gui_app._render_texture:
|
|
rl.end_texture_mode()
|
|
rl.begin_drawing()
|
|
rl.clear_background(rl.BLACK)
|
|
src_rect = rl.Rectangle(0, 0, float(gui_app.width), -float(gui_app.height))
|
|
dst_rect = rl.Rectangle(0, 0, float(gui_app._scaled_width), float(gui_app._scaled_height))
|
|
rl.draw_texture_pro(gui_app._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE)
|
|
rl.end_drawing()
|
|
|
|
|
|
class PrefetchAppC4:
|
|
"""
|
|
GUI application to track git clone operations for C4 (small screen).
|
|
|
|
This class creates a window with progress bars to visualize the status
|
|
of git clone operations. It handles:
|
|
1. Setting up and starting clone operations
|
|
2. Rendering the UI with progress bars and status text
|
|
3. Managing the application lifecycle
|
|
4. Coordinating retries for failed operations
|
|
"""
|
|
|
|
# UI Constants for C4 (536x240 screen)
|
|
PROGRESS_BAR_HEIGHT = 20
|
|
PROGRESS_BAR_WIDTH = 500
|
|
PADDING = 5
|
|
FONT_SIZE = 28
|
|
TITLE_FONT_SIZE = 28
|
|
GAP_AFTER_TITLE = 15
|
|
GAP_BETWEEN_OPERATIONS = 40
|
|
BAR_BG_COLOR = rl.Color(40, 40, 40, 255)
|
|
BAR_FG_COLOR = rl.Color(54, 77, 239, 255)
|
|
WHITE_TEXT_COLOR = rl.Color(255, 255, 255, 255)
|
|
GRAY_TEXT_COLOR = rl.Color(100, 100, 100, 255)
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initialize the prefetch application for C4.
|
|
|
|
Sets up the clone operations but doesn't start them yet.
|
|
"""
|
|
self.initialize_operations()
|
|
|
|
def initialize_operations(self):
|
|
"""
|
|
Initialize or reset the clone operations.
|
|
|
|
Creates GitCloneProgress instances for each repository we need to clone,
|
|
but only if the target directories don't already exist.
|
|
"""
|
|
self.clone_operations = []
|
|
|
|
# Check if the recommended openpilot directory exists
|
|
recommended_exists = os.path.exists(RECOMMENDED_OP_DIR)
|
|
# Check if the alternate openpilot directory exists
|
|
alternate_exists = os.path.exists(ALTERNATE_OP_DIR)
|
|
|
|
# Only create operation for recommended repository if directory doesn't exist
|
|
if not recommended_exists:
|
|
self.clone_operations.append(
|
|
GitCloneProgress(
|
|
["/usr/bin/git", "clone", "--progress",
|
|
f"https://github.com/{RECOMMENDED_OP_USER}/openpilot.git",
|
|
"-b", RECOMMENDED_OP_BRANCH, "--depth=1",
|
|
"--recurse-submodules", RECOMMENDED_OP_DIR],
|
|
f"{RECOMMENDED_OP_USER}/{RECOMMENDED_OP_BRANCH}",
|
|
RECOMMENDED_OP_DIR # Target directory to delete before cloning
|
|
)
|
|
)
|
|
|
|
# Only create operation for alternate repository if directory doesn't exist
|
|
if not alternate_exists:
|
|
self.clone_operations.append(
|
|
GitCloneProgress(
|
|
["/usr/bin/git", "clone", "--progress",
|
|
f"https://github.com/{ALTERNATE_OP_USER}/openpilot.git",
|
|
"-b", ALTERNATE_OP_BRANCH, "--depth=1",
|
|
"--recurse-submodules", ALTERNATE_OP_DIR],
|
|
f"{ALTERNATE_OP_USER}/{ALTERNATE_OP_BRANCH}",
|
|
ALTERNATE_OP_DIR # Target directory to delete before cloning
|
|
)
|
|
)
|
|
|
|
def run(self):
|
|
"""
|
|
Run the prefetch application with retry mechanism.
|
|
|
|
TODO: Implement C4-specific UI rendering.
|
|
For now, this is a stub that needs to be implemented.
|
|
"""
|
|
# Initialize window using gui_app
|
|
gui_app.init_window("TSK Prefetch")
|
|
|
|
# Start clone operations
|
|
self.start_operations()
|
|
|
|
# Main loop
|
|
while not rl.window_should_close():
|
|
# Check for retries in each operation
|
|
for op in self.clone_operations:
|
|
op.check_retry()
|
|
|
|
# Check if all operations are complete or have reached max retries
|
|
all_done = True
|
|
for op in self.clone_operations:
|
|
if not op.completed and (not op.failed or op.retry_count < MAX_RETRIES):
|
|
all_done = False
|
|
break
|
|
|
|
# Exit condition
|
|
if all_done:
|
|
time.sleep(1)
|
|
break
|
|
|
|
# Render the current frame
|
|
self._render_frame()
|
|
|
|
# Cleanup and exit
|
|
rl.close_window()
|
|
|
|
def start_operations(self):
|
|
"""
|
|
Start all clone operations.
|
|
|
|
Initiates the background threads for all git clone operations.
|
|
"""
|
|
for op in self.clone_operations:
|
|
op.start()
|
|
|
|
def _render_frame(self):
|
|
"""
|
|
Render a single frame of the application for C4.
|
|
|
|
Compact layout optimized for 536x240 screen.
|
|
"""
|
|
# Use render texture if scaling is enabled (matches gui_app behavior)
|
|
if gui_app._render_texture:
|
|
rl.begin_texture_mode(gui_app._render_texture)
|
|
rl.clear_background(rl.Color(0, 0, 0, 255))
|
|
else:
|
|
rl.begin_drawing()
|
|
rl.clear_background(rl.Color(0, 0, 0, 255))
|
|
|
|
# Draw title
|
|
title = "Prefetching"
|
|
title_width = rl.measure_text_ex(gui_app.font(), title, self.TITLE_FONT_SIZE, 0).x
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
title,
|
|
rl.Vector2((gui_app.width - title_width) // 2, self.PADDING),
|
|
self.TITLE_FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
|
|
# Draw progress bars for each operation
|
|
y_offset = self.PADDING + self.TITLE_FONT_SIZE + self.GAP_AFTER_TITLE
|
|
|
|
for i, op in enumerate(self.clone_operations):
|
|
# Draw operation title with retry count if applicable
|
|
title_text = op.title
|
|
if op.retry_count > 0:
|
|
title_text += f" (Retry {op.retry_count}/{MAX_RETRIES})"
|
|
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
title_text,
|
|
rl.Vector2(self.PADDING, y_offset),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
y_offset += self.FONT_SIZE + self.PADDING
|
|
|
|
# Draw progress bar background
|
|
bar_x = (gui_app.width - self.PROGRESS_BAR_WIDTH) // 2
|
|
bar_rect = rl.Rectangle(bar_x, y_offset, self.PROGRESS_BAR_WIDTH, self.PROGRESS_BAR_HEIGHT)
|
|
rl.draw_rectangle_rec(bar_rect, self.BAR_BG_COLOR)
|
|
|
|
# Draw progress bar foreground
|
|
progress_width = (op.progress / 100.0) * self.PROGRESS_BAR_WIDTH
|
|
progress_rect = rl.Rectangle(bar_x, y_offset, progress_width, self.PROGRESS_BAR_HEIGHT)
|
|
rl.draw_rectangle_rec(progress_rect, self.BAR_FG_COLOR)
|
|
|
|
# Draw progress percentage
|
|
progress_text = f"{op.progress}%"
|
|
text_width = rl.measure_text_ex(gui_app.font(), progress_text, self.FONT_SIZE, 0).x
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
progress_text,
|
|
rl.Vector2(
|
|
bar_x + (self.PROGRESS_BAR_WIDTH - text_width) // 2,
|
|
y_offset + (self.PROGRESS_BAR_HEIGHT - self.FONT_SIZE) // 2
|
|
),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.WHITE_TEXT_COLOR
|
|
)
|
|
|
|
# Draw status text with retry information if applicable
|
|
status_y = y_offset + self.PROGRESS_BAR_HEIGHT + self.PADDING
|
|
status_text = op.status
|
|
|
|
# Add retry countdown or max retries reached message if applicable
|
|
if op.failed and op.retry_needed and op.retry_count < MAX_RETRIES:
|
|
countdown = max(0, int(RETRY_DELAY - (time.time() - op.retry_timer)))
|
|
status_text += f" - Retrying in {countdown}s..."
|
|
elif op.failed and op.retry_count >= MAX_RETRIES:
|
|
status_text += f" - Max retries reached"
|
|
|
|
# Truncate status text if too long for screen
|
|
max_status_width = gui_app.width - self.PADDING * 2
|
|
status_width = rl.measure_text_ex(gui_app.font(), status_text, self.FONT_SIZE, 0).x
|
|
if status_width > max_status_width:
|
|
# Truncate with ellipsis
|
|
while status_width > max_status_width and len(status_text) > 3:
|
|
status_text = status_text[:-4] + "..."
|
|
status_width = rl.measure_text_ex(gui_app.font(), status_text, self.FONT_SIZE, 0).x
|
|
|
|
# For the last operation, keep same spacing as other operations
|
|
# All status texts use the same PADDING distance from their progress bars
|
|
|
|
rl.draw_text_ex(
|
|
gui_app.font(),
|
|
status_text,
|
|
rl.Vector2(self.PADDING, status_y),
|
|
self.FONT_SIZE,
|
|
0,
|
|
self.GRAY_TEXT_COLOR
|
|
)
|
|
|
|
# Update y_offset for next operation
|
|
if i < len(self.clone_operations) - 1:
|
|
# Move to next operation: skip bar height, small padding, and gap
|
|
y_offset += self.PROGRESS_BAR_HEIGHT + self.PADDING + self.GAP_BETWEEN_OPERATIONS
|
|
|
|
# End rendering and scale if needed (matches gui_app behavior)
|
|
if gui_app._render_texture:
|
|
rl.end_texture_mode()
|
|
rl.begin_drawing()
|
|
rl.clear_background(rl.BLACK)
|
|
src_rect = rl.Rectangle(0, 0, float(gui_app.width), -float(gui_app.height))
|
|
dst_rect = rl.Rectangle(0, 0, float(gui_app._scaled_width), float(gui_app._scaled_height))
|
|
rl.draw_texture_pro(gui_app._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE)
|
|
rl.end_drawing()
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Main Entry Point
|
|
# -------------------------------------------------------------------------
|
|
|
|
def main():
|
|
"""
|
|
Main function to run the prefetch application.
|
|
|
|
Detects device type (C3 vs C4) and loads appropriate GUI.
|
|
This is the entry point when the script is executed directly.
|
|
"""
|
|
# Detect device type based on screen size
|
|
if gui_app.big_ui():
|
|
# C3 device (big screen: 2160x1080)
|
|
app = PrefetchAppC3()
|
|
else:
|
|
# C4 device (small screen: 536x240)
|
|
app = PrefetchAppC4()
|
|
|
|
app.run()
|
|
|
|
# Exit with success code
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|