Files
Jackify/jackify/backend/handlers/progress_state_metrics.py
2026-02-07 18:26:54 +00:00

168 lines
6.6 KiB
Python

"""Metrics and synthetic entry methods for ProgressStateManager (Mixin)."""
import logging
import re
import time
from typing import TYPE_CHECKING
from jackify.shared.progress_models import FileProgress, OperationType, InstallationPhase
if TYPE_CHECKING:
from jackify.backend.handlers.progress_parser import ParsedLine
logger = logging.getLogger(__name__)
class ProgressStateMetricsMixin:
"""Mixin providing metrics augmentation methods."""
def _augment_file_metrics(self, file_progress: FileProgress) -> None:
"""Populate size/speed info to improve UI accuracy."""
now = time.time()
history = self._file_history.get(file_progress.filename)
total_size = file_progress.total_size or (history.get('total') if history else None)
if total_size and file_progress.percent and not file_progress.current_size:
file_progress.current_size = int((file_progress.percent / 100.0) * total_size)
elif file_progress.current_size and not total_size and file_progress.total_size:
total_size = file_progress.total_size
if total_size and not file_progress.total_size:
file_progress.total_size = total_size
current_size = file_progress.current_size or 0
computed_speed = 0.0
if file_progress.speed < 0:
computed_speed = 0.0
if history and current_size:
prev_bytes = history.get('bytes', 0)
prev_time = history.get('time', now)
delta_bytes = current_size - prev_bytes
delta_time = now - prev_time
if delta_bytes >= 0 and delta_time >= 1.0:
computed_speed = delta_bytes / delta_time
elif history.get('computed_speed'):
computed_speed = history.get('computed_speed', 0.0)
file_progress.speed = computed_speed
else:
computed_speed = file_progress.speed
if current_size or total_size:
self._file_history[file_progress.filename] = {
'bytes': current_size,
'time': now,
'total': total_size or (history.get('total') if history else None),
'computed_speed': computed_speed,
}
elif history:
self._file_history[file_progress.filename] = history
def _maybe_add_wabbajack_progress(self, parsed: "ParsedLine") -> bool:
"""Create a synthetic file entry for .wabbajack archive download."""
if not parsed.data_info:
return False
if not parsed.data_info:
return False
current_bytes, total_bytes = parsed.data_info
if total_bytes <= 0:
return False
for fp in self.state.active_files:
if fp.filename.lower().endswith('.wabbajack'):
synthetic_entry = fp
if getattr(fp, self._synthetic_flag, False):
percent = (current_bytes / total_bytes) * 100.0
synthetic_entry.percent = percent
synthetic_entry.current_size = current_bytes
synthetic_entry.total_size = total_bytes
synthetic_entry.last_update = time.time()
self._augment_file_metrics(synthetic_entry)
return True
else:
return False
synthetic_entry = None
for fp in self.state.active_files:
if getattr(fp, self._synthetic_flag, False):
synthetic_entry = fp
break
message = (parsed.message or "")
phase_name = (parsed.phase_name or "").lower()
should_force = 'wabbajack' in message.lower() or 'wabbajack' in phase_name
if not synthetic_entry:
if self._has_real_download_activity() and not should_force:
return False
if self.state.phase not in (InstallationPhase.INITIALIZATION, InstallationPhase.DOWNLOAD) and not should_force:
return False
percent = (current_bytes / total_bytes) * 100.0
if not self._wabbajack_entry_name:
filename_match = re.search(r'([A-Za-z0-9_\-\.]+\.wabbajack)', message, re.IGNORECASE)
if filename_match:
self._wabbajack_entry_name = filename_match.group(1)
if not self._wabbajack_entry_name:
self._wabbajack_entry_name = "Downloading .wabbajack file"
entry_name = self._wabbajack_entry_name
if synthetic_entry:
synthetic_entry.percent = percent
synthetic_entry.current_size = current_bytes
synthetic_entry.total_size = total_bytes
synthetic_entry.last_update = time.time()
self._augment_file_metrics(synthetic_entry)
else:
special_file = FileProgress(
filename=entry_name,
operation=OperationType.DOWNLOAD,
percent=percent,
current_size=current_bytes,
total_size=total_bytes
)
special_file.last_update = time.time()
setattr(special_file, self._synthetic_flag, True)
self._augment_file_metrics(special_file)
self.state.add_file(special_file)
return True
def _has_real_download_activity(self) -> bool:
"""Check if there are real download entries already visible."""
for fp in self.state.active_files:
if getattr(fp, self._synthetic_flag, False):
continue
if fp.operation == OperationType.DOWNLOAD:
return True
return False
def _remove_synthetic_wabbajack(self) -> None:
"""Remove any synthetic .wabbajack entries once real files appear."""
remaining = []
removed = False
for fp in self.state.active_files:
if getattr(fp, self._synthetic_flag, False):
removed = True
self._file_history.pop(fp.filename, None)
continue
remaining.append(fp)
if removed:
self.state.active_files = remaining
def _remove_all_wabbajack_entries(self) -> None:
"""Remove ALL .wabbajack entries when archive download phase starts."""
remaining = []
removed = False
for fp in self.state.active_files:
if fp.filename.lower().endswith('.wabbajack') or 'wabbajack' in fp.filename.lower():
removed = True
self._file_history.pop(fp.filename, None)
continue
remaining.append(fp)
if removed:
self.state.active_files = remaining
self._wabbajack_entry_name = None