mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-01-17 19:47:00 +01:00
Sync from development - prepare for v0.2.0.4
This commit is contained in:
@@ -30,7 +30,8 @@ class AutomatedPrefixService:
|
||||
"""
|
||||
|
||||
def __init__(self, system_info=None):
|
||||
self.scripts_dir = Path.home() / "Jackify/scripts"
|
||||
from jackify.shared.paths import get_jackify_data_dir
|
||||
self.scripts_dir = get_jackify_data_dir() / "scripts"
|
||||
self.scripts_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.system_info = system_info
|
||||
# Use shared timing for consistency across services
|
||||
@@ -749,7 +750,8 @@ echo Creating Proton prefix...
|
||||
timeout /t 3 /nobreak >nul
|
||||
echo Prefix creation complete.
|
||||
"""
|
||||
batch_path = Path.home() / "Jackify/temp_prefix_creation.bat"
|
||||
from jackify.shared.paths import get_jackify_data_dir
|
||||
batch_path = get_jackify_data_dir() / "temp_prefix_creation.bat"
|
||||
batch_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(batch_path, 'w') as f:
|
||||
|
||||
@@ -25,7 +25,8 @@ from jackify.shared.paths import get_jackify_data_dir
|
||||
class ModlistGalleryService:
|
||||
"""Service for fetching and caching modlist metadata from jackify-engine"""
|
||||
|
||||
CACHE_VALIDITY_DAYS = 7 # Refresh cache after 7 days
|
||||
# REMOVED: CACHE_VALIDITY_DAYS - metadata is now always fetched fresh from engine
|
||||
# Images are still cached indefinitely (managed separately)
|
||||
# CRITICAL: Thread lock to prevent concurrent engine calls that could cause recursive spawning
|
||||
_engine_call_lock = threading.Lock()
|
||||
|
||||
@@ -59,31 +60,20 @@ class ModlistGalleryService:
|
||||
"""
|
||||
Fetch modlist metadata from jackify-engine.
|
||||
|
||||
NOTE: Metadata is ALWAYS fetched fresh from the engine to ensure up-to-date
|
||||
version numbers and sizes for frequently-updated modlists. Only images are cached.
|
||||
|
||||
Args:
|
||||
include_validation: Include validation status (slower)
|
||||
include_search_index: Include mod search index (slower)
|
||||
sort_by: Sort order (title, size, date)
|
||||
force_refresh: Force refresh even if cache is valid
|
||||
force_refresh: Deprecated parameter (kept for API compatibility)
|
||||
|
||||
Returns:
|
||||
ModlistMetadataResponse or None if fetch fails
|
||||
"""
|
||||
# Check cache first unless force refresh
|
||||
# If include_search_index is True, check if cache has mods before using it
|
||||
if not force_refresh:
|
||||
cached = self._load_from_cache()
|
||||
if cached and self._is_cache_valid():
|
||||
# If we need search index, check if cached data has mods
|
||||
if include_search_index:
|
||||
# Check if at least one modlist has mods (indicates cache was built with search index)
|
||||
has_mods = any(hasattr(m, 'mods') and m.mods for m in cached.modlists)
|
||||
if has_mods:
|
||||
return cached # Cache has mods, use it
|
||||
# Cache doesn't have mods, need to fetch fresh
|
||||
else:
|
||||
return cached # Don't need search index, use cache
|
||||
|
||||
# Fetch fresh data from jackify-engine
|
||||
# Always fetch fresh data from jackify-engine
|
||||
# The engine itself is fast (~1-2 seconds) and always gets latest metadata
|
||||
try:
|
||||
metadata = self._fetch_from_engine(
|
||||
include_validation=include_validation,
|
||||
@@ -91,6 +81,7 @@ class ModlistGalleryService:
|
||||
sort_by=sort_by
|
||||
)
|
||||
|
||||
# Still save to cache as a fallback for offline scenarios
|
||||
if metadata:
|
||||
self._save_to_cache(metadata)
|
||||
|
||||
@@ -98,7 +89,8 @@ class ModlistGalleryService:
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching modlist metadata: {e}")
|
||||
# Fall back to cache if available
|
||||
print("Falling back to cached metadata (may be outdated)")
|
||||
# Fall back to cache if network/engine fails
|
||||
return self._load_from_cache()
|
||||
|
||||
def _fetch_from_engine(
|
||||
@@ -253,17 +245,6 @@ class ModlistGalleryService:
|
||||
|
||||
return result
|
||||
|
||||
def _is_cache_valid(self) -> bool:
|
||||
"""Check if cache is still valid based on age"""
|
||||
if not self.METADATA_CACHE_FILE.exists():
|
||||
return False
|
||||
|
||||
# Check file modification time
|
||||
mtime = datetime.fromtimestamp(self.METADATA_CACHE_FILE.stat().st_mtime)
|
||||
age = datetime.now() - mtime
|
||||
|
||||
return age < timedelta(days=self.CACHE_VALIDITY_DAYS)
|
||||
|
||||
def download_images(
|
||||
self,
|
||||
game_filter: Optional[str] = None,
|
||||
|
||||
@@ -288,15 +288,6 @@ class ModlistService:
|
||||
# Build command (copied from working code)
|
||||
cmd = [engine_path, 'install', '--show-file-progress']
|
||||
|
||||
# Check GPU setting
|
||||
from jackify.backend.handlers.config_handler import ConfigHandler
|
||||
config_handler = ConfigHandler()
|
||||
gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True)
|
||||
logger.info(f"GPU texture conversion setting: {gpu_enabled}")
|
||||
if not gpu_enabled:
|
||||
cmd.append('--no-gpu')
|
||||
logger.info("Added --no-gpu flag to jackify-engine command")
|
||||
|
||||
modlist_value = context.get('modlist_value')
|
||||
if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value):
|
||||
cmd += ['-w', modlist_value]
|
||||
|
||||
@@ -177,7 +177,7 @@ class NativeSteamOperationsService:
|
||||
|
||||
# Also check additional Steam libraries via libraryfolders.vdf
|
||||
try:
|
||||
from jackify.shared.paths import PathHandler
|
||||
from jackify.backend.handlers.path_handler import PathHandler
|
||||
all_steam_libs = PathHandler.get_all_steam_library_paths()
|
||||
|
||||
for lib_path in all_steam_libs:
|
||||
|
||||
@@ -132,14 +132,31 @@ class ProtontricksDetectionService:
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# Install command
|
||||
install_cmd = ["flatpak", "install", "-u", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"]
|
||||
# Install command - use --user flag for user-level installation (works on Steam Deck)
|
||||
# This avoids requiring system-wide installation permissions
|
||||
install_cmd = ["flatpak", "install", "--user", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"]
|
||||
|
||||
# Use clean environment
|
||||
env = handler._get_clean_subprocess_env()
|
||||
|
||||
# Run installation
|
||||
process = subprocess.run(install_cmd, check=True, text=True, env=env, capture_output=True)
|
||||
# Log the command for debugging
|
||||
logger.debug(f"Running flatpak install command: {' '.join(install_cmd)}")
|
||||
|
||||
# Run installation with timeout (5 minutes should be plenty)
|
||||
process = subprocess.run(
|
||||
install_cmd,
|
||||
check=True,
|
||||
text=True,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
timeout=300 # 5 minute timeout
|
||||
)
|
||||
|
||||
# Log stdout/stderr for debugging (even on success, might contain useful info)
|
||||
if process.stdout:
|
||||
logger.debug(f"Flatpak install stdout: {process.stdout}")
|
||||
if process.stderr:
|
||||
logger.debug(f"Flatpak install stderr: {process.stderr}")
|
||||
|
||||
# Clear cache to force re-detection
|
||||
self._cached_detection_valid = False
|
||||
@@ -152,13 +169,41 @@ class ProtontricksDetectionService:
|
||||
error_msg = "Flatpak command not found. Please install Flatpak first."
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"Flatpak installation failed: {e}"
|
||||
except subprocess.TimeoutExpired:
|
||||
error_msg = "Flatpak installation timed out after 5 minutes. Please check your network connection and try again."
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Include stderr in error message for better debugging
|
||||
stderr_msg = e.stderr.strip() if e.stderr else "No error details available"
|
||||
stdout_msg = e.stdout.strip() if e.stdout else ""
|
||||
|
||||
# Try to extract meaningful error from stderr
|
||||
if stderr_msg:
|
||||
# Common errors: permission denied, network issues, etc.
|
||||
if "permission" in stderr_msg.lower() or "denied" in stderr_msg.lower():
|
||||
error_msg = f"Permission denied. Try running: flatpak install --user flathub com.github.Matoking.protontricks\n\nDetails: {stderr_msg}"
|
||||
elif "network" in stderr_msg.lower() or "connection" in stderr_msg.lower():
|
||||
error_msg = f"Network error during installation. Check your internet connection.\n\nDetails: {stderr_msg}"
|
||||
elif "already installed" in stderr_msg.lower():
|
||||
# This might actually be success - clear cache and re-detect
|
||||
logger.info("Protontricks appears to already be installed (according to flatpak output)")
|
||||
self._cached_detection_valid = False
|
||||
return True, "Protontricks is already installed."
|
||||
else:
|
||||
error_msg = f"Flatpak installation failed:\n\n{stderr_msg}"
|
||||
if stdout_msg:
|
||||
error_msg += f"\n\nOutput: {stdout_msg}"
|
||||
else:
|
||||
error_msg = f"Flatpak installation failed with return code {e.returncode}."
|
||||
if stdout_msg:
|
||||
error_msg += f"\n\nOutput: {stdout_msg}"
|
||||
|
||||
logger.error(f"Flatpak installation error: {error_msg}")
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"Unexpected error during Flatpak installation: {e}"
|
||||
logger.error(error_msg)
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return False, error_msg
|
||||
|
||||
def get_installation_guidance(self) -> str:
|
||||
|
||||
@@ -402,14 +402,15 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
|
||||
if final_check.returncode != 0:
|
||||
logger.info("Steam processes successfully force terminated.")
|
||||
else:
|
||||
report("Failed to terminate Steam processes.")
|
||||
return False
|
||||
# Steam might still be running, but proceed anyway - wait phase will verify
|
||||
logger.warning("Steam processes may still be running after termination attempts. Proceeding to start phase...")
|
||||
report("Steam shutdown incomplete, but proceeding...")
|
||||
else:
|
||||
logger.info("Steam processes successfully terminated.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error during Steam shutdown: {e}")
|
||||
report("Failed to shut down Steam.")
|
||||
return False
|
||||
# Don't fail completely on shutdown errors - proceed to start phase
|
||||
logger.warning(f"Error during Steam shutdown: {e}. Proceeding to start phase anyway...")
|
||||
report("Steam shutdown had issues, but proceeding...")
|
||||
|
||||
report("Steam closed successfully.")
|
||||
|
||||
@@ -427,42 +428,56 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
|
||||
return False
|
||||
else:
|
||||
# All other distros: Use start_steam() which now uses -foreground to ensure GUI opens
|
||||
if not start_steam(
|
||||
steam_started = start_steam(
|
||||
is_steamdeck_flag=_is_steam_deck,
|
||||
is_flatpak_flag=_is_flatpak,
|
||||
env_override=start_env,
|
||||
strategy=strategy,
|
||||
):
|
||||
report("Failed to start Steam.")
|
||||
return False
|
||||
)
|
||||
# Even if start_steam() returns False, Steam might still be starting
|
||||
# Give it a chance by proceeding to wait phase
|
||||
if not steam_started:
|
||||
logger.warning("start_steam() returned False, but proceeding to wait phase in case Steam is starting anyway")
|
||||
report("Steam start command issued, waiting for process...")
|
||||
|
||||
# Wait for Steam to fully initialize
|
||||
# CRITICAL: Use steamwebhelper (actual Steam process), not "steam" (matches steam-powerbuttond, etc.)
|
||||
report("Waiting for Steam to fully start")
|
||||
logger.info("Waiting up to 2 minutes for Steam to fully initialize...")
|
||||
max_startup_wait = 120
|
||||
logger.info("Waiting up to 3 minutes (180 seconds) for Steam to fully initialize...")
|
||||
max_startup_wait = 180 # Increased from 120 to 180 seconds (3 minutes) for slower systems
|
||||
elapsed_wait = 0
|
||||
initial_wait_done = False
|
||||
last_status_log = 0 # Track when we last logged status
|
||||
|
||||
while elapsed_wait < max_startup_wait:
|
||||
try:
|
||||
# Log status every 30 seconds so user knows we're still waiting
|
||||
if elapsed_wait - last_status_log >= 30:
|
||||
remaining = max_startup_wait - elapsed_wait
|
||||
logger.info(f"Still waiting for Steam... ({elapsed_wait}s elapsed, {remaining}s remaining)")
|
||||
if progress_callback:
|
||||
progress_callback(f"Waiting for Steam... ({elapsed_wait}s / {max_startup_wait}s)")
|
||||
last_status_log = elapsed_wait
|
||||
|
||||
# Use steamwebhelper for detection (matches shutdown logic)
|
||||
result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env)
|
||||
if result.returncode == 0:
|
||||
if not initial_wait_done:
|
||||
logger.info("Steam process detected. Waiting additional time for full initialization...")
|
||||
logger.info(f"Steam process detected at {elapsed_wait}s. Waiting additional time for full initialization...")
|
||||
initial_wait_done = True
|
||||
time.sleep(5)
|
||||
elapsed_wait += 5
|
||||
if initial_wait_done and elapsed_wait >= 15:
|
||||
# Require at least 20 seconds of stable detection (increased from 15)
|
||||
if initial_wait_done and elapsed_wait >= 20:
|
||||
final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env)
|
||||
if final_check.returncode == 0:
|
||||
report("Steam started successfully.")
|
||||
logger.info("Steam confirmed running after wait.")
|
||||
logger.info(f"Steam confirmed running after {elapsed_wait}s wait.")
|
||||
return True
|
||||
else:
|
||||
logger.warning("Steam process disappeared during final initialization wait.")
|
||||
break
|
||||
logger.warning("Steam process disappeared during final initialization wait, continuing to wait...")
|
||||
# Don't break - continue waiting in case Steam is still starting
|
||||
initial_wait_done = False # Reset to allow re-detection
|
||||
else:
|
||||
logger.debug(f"Steam process not yet detected. Waiting... ({elapsed_wait + 5}s)")
|
||||
time.sleep(5)
|
||||
@@ -472,6 +487,7 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
|
||||
time.sleep(5)
|
||||
elapsed_wait += 5
|
||||
|
||||
report("Steam did not start within timeout.")
|
||||
logger.error("Steam failed to start/initialize within the allowed time.")
|
||||
# Only reach here if we've waited the full duration
|
||||
report(f"Steam did not start within {max_startup_wait}s timeout.")
|
||||
logger.error(f"Steam failed to start/initialize within the allowed time ({elapsed_wait}s elapsed).")
|
||||
return False
|
||||
@@ -271,9 +271,9 @@ class UpdateService:
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
downloaded_size = 0
|
||||
|
||||
# Create update directory in user's home directory
|
||||
home_dir = Path.home()
|
||||
update_dir = home_dir / "Jackify" / "updates"
|
||||
# Create update directory in user's data directory
|
||||
from jackify.shared.paths import get_jackify_data_dir
|
||||
update_dir = get_jackify_data_dir() / "updates"
|
||||
update_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
temp_file = update_dir / f"Jackify-{update_info.version}.AppImage"
|
||||
@@ -345,9 +345,9 @@ class UpdateService:
|
||||
Path to helper script, or None if creation failed
|
||||
"""
|
||||
try:
|
||||
# Create update directory in user's home directory
|
||||
home_dir = Path.home()
|
||||
update_dir = home_dir / "Jackify" / "updates"
|
||||
# Create update directory in user's data directory
|
||||
from jackify.shared.paths import get_jackify_data_dir
|
||||
update_dir = get_jackify_data_dir() / "updates"
|
||||
update_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
helper_script = update_dir / "update_helper.sh"
|
||||
|
||||
Reference in New Issue
Block a user