Sync from development - prepare for v0.2.0.4

This commit is contained in:
Omni
2025-12-23 21:49:18 +00:00
parent 523681a254
commit a7ed4b2a1e
62 changed files with 575 additions and 429 deletions

View File

@@ -1,5 +1,27 @@
# Jackify Changelog # Jackify Changelog
## v0.2.0.4 - Bugfixes & Improvements
**Release Date:** 2025-12-23
### Engine Updates
- **jackify-engine 0.4.3**: Fixed case sensitivity issues, archive extraction crashes, and improved error messages
### Bug Fixes
- Fixed modlist gallery metadata showing outdated versions (now always fetches fresh data)
- Fixed hardcoded ~/Jackify paths preventing custom data directory settings
- Fixed update check blocking GUI startup
- Improved Steam restart reliability (3-minute timeout, better error handling)
- Fixed Protontricks Flatpak installation on Steam Deck
### Backend Changes
- GPU texture conversion now always enabled (config setting deprecated)
### UI Improvements
- Redesigned modlist detail view to show more of hero image
- Improved gallery loading with animated feedback and faster initial load
---
## v0.2.0.3 - Engine Bugfix & Settings Cleanup ## v0.2.0.3 - Engine Bugfix & Settings Cleanup
**Release Date:** 2025-12-21 **Release Date:** 2025-12-21

View File

@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
Wabbajack modlists natively on Linux systems. Wabbajack modlists natively on Linux systems.
""" """
__version__ = "0.2.0.3" __version__ = "0.2.0.4"

View File

@@ -680,7 +680,8 @@ class ModlistInstallCLI:
start_time = time.time() start_time = time.time()
# --- BEGIN: TEE LOGGING SETUP & LOG ROTATION --- # --- BEGIN: TEE LOGGING SETUP & LOG ROTATION ---
log_dir = Path.home() / "Jackify" / "logs" from jackify.shared.paths import get_jackify_logs_dir
log_dir = get_jackify_logs_dir()
log_dir.mkdir(parents=True, exist_ok=True) log_dir.mkdir(parents=True, exist_ok=True)
workflow_log_path = log_dir / "Modlist_Install_workflow.log" workflow_log_path = log_dir / "Modlist_Install_workflow.log"
# Log rotation: keep last 3 logs, 1MB each (adjust as needed) # Log rotation: keep last 3 logs, 1MB each (adjust as needed)
@@ -775,12 +776,6 @@ class ModlistInstallCLI:
cmd.append('--debug') cmd.append('--debug')
self.logger.info("Adding --debug flag to jackify-engine") self.logger.info("Adding --debug flag to jackify-engine")
# Check GPU setting and add --no-gpu flag if disabled
gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True)
if not gpu_enabled:
cmd.append('--no-gpu')
self.logger.info("GPU texture conversion disabled - adding --no-gpu flag to jackify-engine")
# Store original environment values to restore later # Store original environment values to restore later
original_env_values = { original_env_values = {
'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'), 'NEXUS_API_KEY': os.environ.get('NEXUS_API_KEY'),

View File

@@ -390,6 +390,14 @@ class ConfigHandler:
try: try:
from Crypto.Cipher import AES from Crypto.Cipher import AES
# Check if MODE_GCM is available (pycryptodome has it, old pycrypto doesn't)
if not hasattr(AES, 'MODE_GCM'):
# Fallback to base64 decode if old pycrypto is installed
try:
return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8')
except:
return None
# Derive 32-byte AES key # Derive 32-byte AES key
key = base64.urlsafe_b64decode(self._get_encryption_key()) key = base64.urlsafe_b64decode(self._get_encryption_key())
@@ -411,6 +419,12 @@ class ConfigHandler:
return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8') return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8')
except: except:
return None return None
except AttributeError:
# Old pycrypto doesn't have MODE_GCM, fallback to base64
try:
return base64.b64decode(encrypted_key.encode('utf-8')).decode('utf-8')
except:
return None
except Exception as e: except Exception as e:
# Might be old base64-only format, try decoding # Might be old base64-only format, try decoding
try: try:

View File

@@ -1196,7 +1196,8 @@ class InstallWabbajackHandler:
"""Displays the final success message and next steps.""" """Displays the final success message and next steps."""
# Basic log file path (assuming standard location) # Basic log file path (assuming standard location)
# TODO: Get log file path more reliably if needed # TODO: Get log file path more reliably if needed
log_path = Path.home() / "Jackify" / "logs" / "jackify-cli.log" from jackify.shared.paths import get_jackify_logs_dir
log_path = get_jackify_logs_dir() / "jackify-cli.log"
print("\n───────────────────────────────────────────────────────────────────") print("\n───────────────────────────────────────────────────────────────────")
print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}") print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}")

View File

@@ -21,7 +21,8 @@ class LoggingHandler:
logger = LoggingHandler().setup_logger('install_wabbajack', 'jackify-install-wabbajack.log') logger = LoggingHandler().setup_logger('install_wabbajack', 'jackify-install-wabbajack.log')
""" """
def __init__(self): def __init__(self):
self.log_dir = Path.home() / "Jackify" / "logs" from jackify.shared.paths import get_jackify_logs_dir
self.log_dir = get_jackify_logs_dir()
self.ensure_log_directory() self.ensure_log_directory()
def ensure_log_directory(self) -> None: def ensure_log_directory(self) -> None:

View File

@@ -558,7 +558,8 @@ class ModlistInstallCLI:
start_time = time.time() start_time = time.time()
# --- BEGIN: TEE LOGGING SETUP & LOG ROTATION --- # --- BEGIN: TEE LOGGING SETUP & LOG ROTATION ---
log_dir = Path.home() / "Jackify" / "logs" from jackify.shared.paths import get_jackify_logs_dir
log_dir = get_jackify_logs_dir()
log_dir.mkdir(parents=True, exist_ok=True) log_dir.mkdir(parents=True, exist_ok=True)
workflow_log_path = log_dir / "Modlist_Install_workflow.log" workflow_log_path = log_dir / "Modlist_Install_workflow.log"
# Log rotation: keep last 3 logs, 1MB each (adjust as needed) # Log rotation: keep last 3 logs, 1MB each (adjust as needed)
@@ -644,12 +645,6 @@ class ModlistInstallCLI:
cmd.append('--debug') cmd.append('--debug')
self.logger.info("Debug mode enabled in config - passing --debug flag to jackify-engine") self.logger.info("Debug mode enabled in config - passing --debug flag to jackify-engine")
# Check GPU setting and add --no-gpu flag if disabled
gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True)
if not gpu_enabled:
cmd.append('--no-gpu')
self.logger.info("GPU texture conversion disabled - passing --no-gpu flag to jackify-engine")
# Determine if this is a local .wabbajack file or an online modlist # Determine if this is a local .wabbajack file or an online modlist
modlist_value = self.context.get('modlist_value') modlist_value = self.context.get('modlist_value')
machineid = self.context.get('machineid') machineid = self.context.get('machineid')

View File

@@ -143,6 +143,11 @@ class OAuthTokenHandler:
try: try:
from Crypto.Cipher import AES from Crypto.Cipher import AES
# Check if MODE_GCM is available (pycryptodome has it, old pycrypto doesn't)
if not hasattr(AES, 'MODE_GCM'):
logger.error("pycryptodome required for token decryption (pycrypto doesn't support MODE_GCM)")
return None
# Derive 32-byte AES key from encryption_key # Derive 32-byte AES key from encryption_key
key = base64.urlsafe_b64decode(self._encryption_key) key = base64.urlsafe_b64decode(self._encryption_key)
@@ -163,6 +168,9 @@ class OAuthTokenHandler:
except ImportError: except ImportError:
logger.error("pycryptodome package not available for token decryption") logger.error("pycryptodome package not available for token decryption")
return None return None
except AttributeError:
logger.error("pycryptodome required for token decryption (pycrypto doesn't support MODE_GCM)")
return None
except Exception as e: except Exception as e:
logger.error(f"Failed to decrypt data: {e}") logger.error(f"Failed to decrypt data: {e}")
return None return None

View File

@@ -23,7 +23,8 @@ from .subprocess_utils import get_clean_subprocess_env
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Define default TTW_Linux_Installer paths # Define default TTW_Linux_Installer paths
JACKIFY_BASE_DIR = Path.home() / "Jackify" from jackify.shared.paths import get_jackify_data_dir
JACKIFY_BASE_DIR = get_jackify_data_dir()
DEFAULT_TTW_INSTALLER_DIR = JACKIFY_BASE_DIR / "TTW_Linux_Installer" DEFAULT_TTW_INSTALLER_DIR = JACKIFY_BASE_DIR / "TTW_Linux_Installer"
TTW_INSTALLER_EXECUTABLE_NAME = "ttw_linux_gui" # Same executable, runs in CLI mode with args TTW_INSTALLER_EXECUTABLE_NAME = "ttw_linux_gui" # Same executable, runs in CLI mode with args

View File

@@ -1196,7 +1196,8 @@ class InstallWabbajackHandler:
"""Displays the final success message and next steps.""" """Displays the final success message and next steps."""
# Basic log file path (assuming standard location) # Basic log file path (assuming standard location)
# TODO: Get log file path more reliably if needed # TODO: Get log file path more reliably if needed
log_path = Path.home() / "Jackify" / "logs" / "jackify-cli.log" from jackify.shared.paths import get_jackify_logs_dir
log_path = get_jackify_logs_dir() / "jackify-cli.log"
print("\n───────────────────────────────────────────────────────────────────") print("\n───────────────────────────────────────────────────────────────────")
print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}") print(f"{COLOR_INFO}Wabbajack Installation Completed Successfully!{COLOR_RESET}")

View File

@@ -30,7 +30,8 @@ class AutomatedPrefixService:
""" """
def __init__(self, system_info=None): 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.scripts_dir.mkdir(parents=True, exist_ok=True)
self.system_info = system_info self.system_info = system_info
# Use shared timing for consistency across services # Use shared timing for consistency across services
@@ -749,7 +750,8 @@ echo Creating Proton prefix...
timeout /t 3 /nobreak >nul timeout /t 3 /nobreak >nul
echo Prefix creation complete. 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) batch_path.parent.mkdir(parents=True, exist_ok=True)
with open(batch_path, 'w') as f: with open(batch_path, 'w') as f:

View File

@@ -25,7 +25,8 @@ from jackify.shared.paths import get_jackify_data_dir
class ModlistGalleryService: class ModlistGalleryService:
"""Service for fetching and caching modlist metadata from jackify-engine""" """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 # CRITICAL: Thread lock to prevent concurrent engine calls that could cause recursive spawning
_engine_call_lock = threading.Lock() _engine_call_lock = threading.Lock()
@@ -59,31 +60,20 @@ class ModlistGalleryService:
""" """
Fetch modlist metadata from jackify-engine. 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: Args:
include_validation: Include validation status (slower) include_validation: Include validation status (slower)
include_search_index: Include mod search index (slower) include_search_index: Include mod search index (slower)
sort_by: Sort order (title, size, date) 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: Returns:
ModlistMetadataResponse or None if fetch fails ModlistMetadataResponse or None if fetch fails
""" """
# Check cache first unless force refresh # Always fetch fresh data from jackify-engine
# If include_search_index is True, check if cache has mods before using it # The engine itself is fast (~1-2 seconds) and always gets latest metadata
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
try: try:
metadata = self._fetch_from_engine( metadata = self._fetch_from_engine(
include_validation=include_validation, include_validation=include_validation,
@@ -91,6 +81,7 @@ class ModlistGalleryService:
sort_by=sort_by sort_by=sort_by
) )
# Still save to cache as a fallback for offline scenarios
if metadata: if metadata:
self._save_to_cache(metadata) self._save_to_cache(metadata)
@@ -98,7 +89,8 @@ class ModlistGalleryService:
except Exception as e: except Exception as e:
print(f"Error fetching modlist metadata: {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() return self._load_from_cache()
def _fetch_from_engine( def _fetch_from_engine(
@@ -253,17 +245,6 @@ class ModlistGalleryService:
return result 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( def download_images(
self, self,
game_filter: Optional[str] = None, game_filter: Optional[str] = None,

View File

@@ -288,15 +288,6 @@ class ModlistService:
# Build command (copied from working code) # Build command (copied from working code)
cmd = [engine_path, 'install', '--show-file-progress'] 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') modlist_value = context.get('modlist_value')
if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value): if modlist_value and modlist_value.endswith('.wabbajack') and os.path.isfile(modlist_value):
cmd += ['-w', modlist_value] cmd += ['-w', modlist_value]

View File

@@ -177,7 +177,7 @@ class NativeSteamOperationsService:
# Also check additional Steam libraries via libraryfolders.vdf # Also check additional Steam libraries via libraryfolders.vdf
try: try:
from jackify.shared.paths import PathHandler from jackify.backend.handlers.path_handler import PathHandler
all_steam_libs = PathHandler.get_all_steam_library_paths() all_steam_libs = PathHandler.get_all_steam_library_paths()
for lib_path in all_steam_libs: for lib_path in all_steam_libs:

View File

@@ -132,14 +132,31 @@ class ProtontricksDetectionService:
logger.error(error_msg) logger.error(error_msg)
return False, error_msg return False, error_msg
# Install command # Install command - use --user flag for user-level installation (works on Steam Deck)
install_cmd = ["flatpak", "install", "-u", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"] # This avoids requiring system-wide installation permissions
install_cmd = ["flatpak", "install", "--user", "-y", "--noninteractive", "flathub", "com.github.Matoking.protontricks"]
# Use clean environment # Use clean environment
env = handler._get_clean_subprocess_env() env = handler._get_clean_subprocess_env()
# Run installation # Log the command for debugging
process = subprocess.run(install_cmd, check=True, text=True, env=env, capture_output=True) 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 # Clear cache to force re-detection
self._cached_detection_valid = False self._cached_detection_valid = False
@@ -152,13 +169,41 @@ class ProtontricksDetectionService:
error_msg = "Flatpak command not found. Please install Flatpak first." error_msg = "Flatpak command not found. Please install Flatpak first."
logger.error(error_msg) logger.error(error_msg)
return False, error_msg return False, error_msg
except subprocess.CalledProcessError as e: except subprocess.TimeoutExpired:
error_msg = f"Flatpak installation failed: {e}" error_msg = "Flatpak installation timed out after 5 minutes. Please check your network connection and try again."
logger.error(error_msg) logger.error(error_msg)
return False, 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: except Exception as e:
error_msg = f"Unexpected error during Flatpak installation: {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 return False, error_msg
def get_installation_guidance(self) -> str: def get_installation_guidance(self) -> str:

View File

@@ -402,14 +402,15 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
if final_check.returncode != 0: if final_check.returncode != 0:
logger.info("Steam processes successfully force terminated.") logger.info("Steam processes successfully force terminated.")
else: else:
report("Failed to terminate Steam processes.") # Steam might still be running, but proceed anyway - wait phase will verify
return False logger.warning("Steam processes may still be running after termination attempts. Proceeding to start phase...")
report("Steam shutdown incomplete, but proceeding...")
else: else:
logger.info("Steam processes successfully terminated.") logger.info("Steam processes successfully terminated.")
except Exception as e: except Exception as e:
logger.error(f"Error during Steam shutdown: {e}") # Don't fail completely on shutdown errors - proceed to start phase
report("Failed to shut down Steam.") logger.warning(f"Error during Steam shutdown: {e}. Proceeding to start phase anyway...")
return False report("Steam shutdown had issues, but proceeding...")
report("Steam closed successfully.") report("Steam closed successfully.")
@@ -427,42 +428,56 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
return False return False
else: else:
# All other distros: Use start_steam() which now uses -foreground to ensure GUI opens # 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_steamdeck_flag=_is_steam_deck,
is_flatpak_flag=_is_flatpak, is_flatpak_flag=_is_flatpak,
env_override=start_env, env_override=start_env,
strategy=strategy, strategy=strategy,
): )
report("Failed to start Steam.") # Even if start_steam() returns False, Steam might still be starting
return False # 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 # Wait for Steam to fully initialize
# CRITICAL: Use steamwebhelper (actual Steam process), not "steam" (matches steam-powerbuttond, etc.) # CRITICAL: Use steamwebhelper (actual Steam process), not "steam" (matches steam-powerbuttond, etc.)
report("Waiting for Steam to fully start") report("Waiting for Steam to fully start")
logger.info("Waiting up to 2 minutes for Steam to fully initialize...") logger.info("Waiting up to 3 minutes (180 seconds) for Steam to fully initialize...")
max_startup_wait = 120 max_startup_wait = 180 # Increased from 120 to 180 seconds (3 minutes) for slower systems
elapsed_wait = 0 elapsed_wait = 0
initial_wait_done = False initial_wait_done = False
last_status_log = 0 # Track when we last logged status
while elapsed_wait < max_startup_wait: while elapsed_wait < max_startup_wait:
try: 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) # Use steamwebhelper for detection (matches shutdown logic)
result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env) result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env)
if result.returncode == 0: if result.returncode == 0:
if not initial_wait_done: 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 initial_wait_done = True
time.sleep(5) time.sleep(5)
elapsed_wait += 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) final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env)
if final_check.returncode == 0: if final_check.returncode == 0:
report("Steam started successfully.") report("Steam started successfully.")
logger.info("Steam confirmed running after wait.") logger.info(f"Steam confirmed running after {elapsed_wait}s wait.")
return True return True
else: else:
logger.warning("Steam process disappeared during final initialization wait.") logger.warning("Steam process disappeared during final initialization wait, continuing to wait...")
break # Don't break - continue waiting in case Steam is still starting
initial_wait_done = False # Reset to allow re-detection
else: else:
logger.debug(f"Steam process not yet detected. Waiting... ({elapsed_wait + 5}s)") logger.debug(f"Steam process not yet detected. Waiting... ({elapsed_wait + 5}s)")
time.sleep(5) time.sleep(5)
@@ -472,6 +487,7 @@ def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = No
time.sleep(5) time.sleep(5)
elapsed_wait += 5 elapsed_wait += 5
report("Steam did not start within timeout.") # Only reach here if we've waited the full duration
logger.error("Steam failed to start/initialize within the allowed time.") 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 return False

View File

@@ -271,9 +271,9 @@ class UpdateService:
total_size = int(response.headers.get('content-length', 0)) total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0 downloaded_size = 0
# Create update directory in user's home directory # Create update directory in user's data directory
home_dir = Path.home() from jackify.shared.paths import get_jackify_data_dir
update_dir = home_dir / "Jackify" / "updates" update_dir = get_jackify_data_dir() / "updates"
update_dir.mkdir(parents=True, exist_ok=True) update_dir.mkdir(parents=True, exist_ok=True)
temp_file = update_dir / f"Jackify-{update_info.version}.AppImage" 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 Path to helper script, or None if creation failed
""" """
try: try:
# Create update directory in user's home directory # Create update directory in user's data directory
home_dir = Path.home() from jackify.shared.paths import get_jackify_data_dir
update_dir = home_dir / "Jackify" / "updates" update_dir = get_jackify_data_dir() / "updates"
update_dir.mkdir(parents=True, exist_ok=True) update_dir.mkdir(parents=True, exist_ok=True)
helper_script = update_dir / "update_helper.sh" helper_script = update_dir / "update_helper.sh"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,7 +7,7 @@
"targets": { "targets": {
".NETCoreApp,Version=v8.0": {}, ".NETCoreApp,Version=v8.0": {},
".NETCoreApp,Version=v8.0/linux-x64": { ".NETCoreApp,Version=v8.0/linux-x64": {
"jackify-engine/0.4.2": { "jackify-engine/0.4.3": {
"dependencies": { "dependencies": {
"Markdig": "0.40.0", "Markdig": "0.40.0",
"Microsoft.Extensions.Configuration.Json": "9.0.1", "Microsoft.Extensions.Configuration.Json": "9.0.1",
@@ -22,16 +22,16 @@
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.CLI.Builder": "0.4.2", "Wabbajack.CLI.Builder": "0.4.3",
"Wabbajack.Downloaders.Bethesda": "0.4.2", "Wabbajack.Downloaders.Bethesda": "0.4.3",
"Wabbajack.Downloaders.Dispatcher": "0.4.2", "Wabbajack.Downloaders.Dispatcher": "0.4.3",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Networking.Discord": "0.4.2", "Wabbajack.Networking.Discord": "0.4.3",
"Wabbajack.Networking.GitHub": "0.4.2", "Wabbajack.Networking.GitHub": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2", "Wabbajack.Paths.IO": "0.4.3",
"Wabbajack.Server.Lib": "0.4.2", "Wabbajack.Server.Lib": "0.4.3",
"Wabbajack.Services.OSIntegrated": "0.4.2", "Wabbajack.Services.OSIntegrated": "0.4.3",
"Wabbajack.VFS": "0.4.2", "Wabbajack.VFS": "0.4.3",
"MegaApiClient": "1.0.0.0", "MegaApiClient": "1.0.0.0",
"runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.22" "runtimepack.Microsoft.NETCore.App.Runtime.linux-x64": "8.0.22"
}, },
@@ -1781,7 +1781,7 @@
} }
} }
}, },
"Wabbajack.CLI.Builder/0.4.2": { "Wabbajack.CLI.Builder/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.Configuration.Json": "9.0.1", "Microsoft.Extensions.Configuration.Json": "9.0.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -1791,109 +1791,109 @@
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.CommandLine": "2.0.0-beta4.22272.1", "System.CommandLine": "2.0.0-beta4.22272.1",
"System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1", "System.CommandLine.NamingConventionBinder": "2.0.0-beta4.22272.1",
"Wabbajack.Paths": "0.4.2" "Wabbajack.Paths": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.CLI.Builder.dll": {} "Wabbajack.CLI.Builder.dll": {}
} }
}, },
"Wabbajack.Common/0.4.2": { "Wabbajack.Common/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"System.Reactive": "6.0.1", "System.Reactive": "6.0.1",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Common.dll": {} "Wabbajack.Common.dll": {}
} }
}, },
"Wabbajack.Compiler/0.4.2": { "Wabbajack.Compiler/0.4.3": {
"dependencies": { "dependencies": {
"F23.StringSimilarity": "6.0.0", "F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Dispatcher": "0.4.2", "Wabbajack.Downloaders.Dispatcher": "0.4.3",
"Wabbajack.Installer": "0.4.2", "Wabbajack.Installer": "0.4.3",
"Wabbajack.VFS": "0.4.2", "Wabbajack.VFS": "0.4.3",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Compiler.dll": {} "Wabbajack.Compiler.dll": {}
} }
}, },
"Wabbajack.Compression.BSA/0.4.2": { "Wabbajack.Compression.BSA/0.4.3": {
"dependencies": { "dependencies": {
"K4os.Compression.LZ4.Streams": "1.3.8", "K4os.Compression.LZ4.Streams": "1.3.8",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2", "SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.DTOs": "0.4.2" "Wabbajack.DTOs": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.BSA.dll": {} "Wabbajack.Compression.BSA.dll": {}
} }
}, },
"Wabbajack.Compression.Zip/0.4.2": { "Wabbajack.Compression.Zip/0.4.3": {
"dependencies": { "dependencies": {
"Wabbajack.IO.Async": "0.4.2" "Wabbajack.IO.Async": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Compression.Zip.dll": {} "Wabbajack.Compression.Zip.dll": {}
} }
}, },
"Wabbajack.Configuration/0.4.2": { "Wabbajack.Configuration/0.4.3": {
"runtime": { "runtime": {
"Wabbajack.Configuration.dll": {} "Wabbajack.Configuration.dll": {}
} }
}, },
"Wabbajack.Downloaders.Bethesda/0.4.2": { "Wabbajack.Downloaders.Bethesda/0.4.3": {
"dependencies": { "dependencies": {
"LibAES-CTR": "1.1.0", "LibAES-CTR": "1.1.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SharpZipLib": "1.4.2", "SharpZipLib": "1.4.2",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.BethesdaNet": "0.4.2" "Wabbajack.Networking.BethesdaNet": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Bethesda.dll": {} "Wabbajack.Downloaders.Bethesda.dll": {}
} }
}, },
"Wabbajack.Downloaders.Dispatcher/0.4.2": { "Wabbajack.Downloaders.Dispatcher/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Bethesda": "0.4.2", "Wabbajack.Downloaders.Bethesda": "0.4.3",
"Wabbajack.Downloaders.GameFile": "0.4.2", "Wabbajack.Downloaders.GameFile": "0.4.3",
"Wabbajack.Downloaders.GoogleDrive": "0.4.2", "Wabbajack.Downloaders.GoogleDrive": "0.4.3",
"Wabbajack.Downloaders.Http": "0.4.2", "Wabbajack.Downloaders.Http": "0.4.3",
"Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.2", "Wabbajack.Downloaders.IPS4OAuth2Downloader": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Downloaders.Manual": "0.4.2", "Wabbajack.Downloaders.Manual": "0.4.3",
"Wabbajack.Downloaders.MediaFire": "0.4.2", "Wabbajack.Downloaders.MediaFire": "0.4.3",
"Wabbajack.Downloaders.Mega": "0.4.2", "Wabbajack.Downloaders.Mega": "0.4.3",
"Wabbajack.Downloaders.ModDB": "0.4.2", "Wabbajack.Downloaders.ModDB": "0.4.3",
"Wabbajack.Downloaders.Nexus": "0.4.2", "Wabbajack.Downloaders.Nexus": "0.4.3",
"Wabbajack.Downloaders.VerificationCache": "0.4.2", "Wabbajack.Downloaders.VerificationCache": "0.4.3",
"Wabbajack.Downloaders.WabbajackCDN": "0.4.2", "Wabbajack.Downloaders.WabbajackCDN": "0.4.3",
"Wabbajack.Networking.WabbajackClientApi": "0.4.2" "Wabbajack.Networking.WabbajackClientApi": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Dispatcher.dll": {} "Wabbajack.Downloaders.Dispatcher.dll": {}
} }
}, },
"Wabbajack.Downloaders.GameFile/0.4.2": { "Wabbajack.Downloaders.GameFile/0.4.3": {
"dependencies": { "dependencies": {
"GameFinder.StoreHandlers.EADesktop": "4.5.0", "GameFinder.StoreHandlers.EADesktop": "4.5.0",
"GameFinder.StoreHandlers.EGS": "4.5.0", "GameFinder.StoreHandlers.EGS": "4.5.0",
@@ -1903,360 +1903,360 @@
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.VFS": "0.4.2" "Wabbajack.VFS": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GameFile.dll": {} "Wabbajack.Downloaders.GameFile.dll": {}
} }
}, },
"Wabbajack.Downloaders.GoogleDrive/0.4.2": { "Wabbajack.Downloaders.GoogleDrive/0.4.3": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.AspNetCore.Http.Extensions": "2.3.0", "Microsoft.AspNetCore.Http.Extensions": "2.3.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.GoogleDrive.dll": {} "Wabbajack.Downloaders.GoogleDrive.dll": {}
} }
}, },
"Wabbajack.Downloaders.Http/0.4.2": { "Wabbajack.Downloaders.Http/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.BethesdaNet": "0.4.2", "Wabbajack.Networking.BethesdaNet": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2", "Wabbajack.Networking.Http.Interfaces": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Http.dll": {} "Wabbajack.Downloaders.Http.dll": {}
} }
}, },
"Wabbajack.Downloaders.Interfaces/0.4.2": { "Wabbajack.Downloaders.Interfaces/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Compression.Zip": "0.4.2", "Wabbajack.Compression.Zip": "0.4.3",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Interfaces.dll": {} "Wabbajack.Downloaders.Interfaces.dll": {}
} }
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.2": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.3": {
"dependencies": { "dependencies": {
"F23.StringSimilarity": "6.0.0", "F23.StringSimilarity": "6.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {} "Wabbajack.Downloaders.IPS4OAuth2Downloader.dll": {}
} }
}, },
"Wabbajack.Downloaders.Manual/0.4.2": { "Wabbajack.Downloaders.Manual/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2" "Wabbajack.Downloaders.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Manual.dll": {} "Wabbajack.Downloaders.Manual.dll": {}
} }
}, },
"Wabbajack.Downloaders.MediaFire/0.4.2": { "Wabbajack.Downloaders.MediaFire/0.4.3": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.MediaFire.dll": {} "Wabbajack.Downloaders.MediaFire.dll": {}
} }
}, },
"Wabbajack.Downloaders.Mega/0.4.2": { "Wabbajack.Downloaders.Mega/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Mega.dll": {} "Wabbajack.Downloaders.Mega.dll": {}
} }
}, },
"Wabbajack.Downloaders.ModDB/0.4.2": { "Wabbajack.Downloaders.ModDB/0.4.3": {
"dependencies": { "dependencies": {
"HtmlAgilityPack": "1.11.72", "HtmlAgilityPack": "1.11.72",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.ModDB.dll": {} "Wabbajack.Downloaders.ModDB.dll": {}
} }
}, },
"Wabbajack.Downloaders.Nexus/0.4.2": { "Wabbajack.Downloaders.Nexus/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2", "Wabbajack.Networking.Http.Interfaces": "0.4.3",
"Wabbajack.Networking.NexusApi": "0.4.2", "Wabbajack.Networking.NexusApi": "0.4.3",
"Wabbajack.Paths": "0.4.2" "Wabbajack.Paths": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.Nexus.dll": {} "Wabbajack.Downloaders.Nexus.dll": {}
} }
}, },
"Wabbajack.Downloaders.VerificationCache/0.4.2": { "Wabbajack.Downloaders.VerificationCache/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Stub.System.Data.SQLite.Core.NetStandard": "1.0.119", "Stub.System.Data.SQLite.Core.NetStandard": "1.0.119",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.VerificationCache.dll": {} "Wabbajack.Downloaders.VerificationCache.dll": {}
} }
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.4.2": { "Wabbajack.Downloaders.WabbajackCDN/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Microsoft.Toolkit.HighPerformance": "7.1.2", "Microsoft.Toolkit.HighPerformance": "7.1.2",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.RateLimiter": "0.4.2" "Wabbajack.RateLimiter": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Downloaders.WabbajackCDN.dll": {} "Wabbajack.Downloaders.WabbajackCDN.dll": {}
} }
}, },
"Wabbajack.DTOs/0.4.2": { "Wabbajack.DTOs/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Paths": "0.4.2" "Wabbajack.Paths": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.DTOs.dll": {} "Wabbajack.DTOs.dll": {}
} }
}, },
"Wabbajack.FileExtractor/0.4.2": { "Wabbajack.FileExtractor/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"OMODFramework": "3.0.1", "OMODFramework": "3.0.1",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Compression.BSA": "0.4.2", "Wabbajack.Compression.BSA": "0.4.3",
"Wabbajack.Hashing.PHash": "0.4.2", "Wabbajack.Hashing.PHash": "0.4.3",
"Wabbajack.Paths": "0.4.2" "Wabbajack.Paths": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.FileExtractor.dll": {} "Wabbajack.FileExtractor.dll": {}
} }
}, },
"Wabbajack.Hashing.PHash/0.4.2": { "Wabbajack.Hashing.PHash/0.4.3": {
"dependencies": { "dependencies": {
"BCnEncoder.Net.ImageSharp": "1.1.1", "BCnEncoder.Net.ImageSharp": "1.1.1",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Shipwreck.Phash": "0.5.0", "Shipwreck.Phash": "0.5.0",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.PHash.dll": {} "Wabbajack.Hashing.PHash.dll": {}
} }
}, },
"Wabbajack.Hashing.xxHash64/0.4.2": { "Wabbajack.Hashing.xxHash64/0.4.3": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"Wabbajack.RateLimiter": "0.4.2" "Wabbajack.RateLimiter": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Hashing.xxHash64.dll": {} "Wabbajack.Hashing.xxHash64.dll": {}
} }
}, },
"Wabbajack.Installer/0.4.2": { "Wabbajack.Installer/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"Octopus.Octodiff": "2.0.548", "Octopus.Octodiff": "2.0.548",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Downloaders.Dispatcher": "0.4.2", "Wabbajack.Downloaders.Dispatcher": "0.4.3",
"Wabbajack.Downloaders.GameFile": "0.4.2", "Wabbajack.Downloaders.GameFile": "0.4.3",
"Wabbajack.FileExtractor": "0.4.2", "Wabbajack.FileExtractor": "0.4.3",
"Wabbajack.Networking.WabbajackClientApi": "0.4.2", "Wabbajack.Networking.WabbajackClientApi": "0.4.3",
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2", "Wabbajack.Paths.IO": "0.4.3",
"Wabbajack.VFS": "0.4.2", "Wabbajack.VFS": "0.4.3",
"ini-parser-netstandard": "2.5.2" "ini-parser-netstandard": "2.5.2"
}, },
"runtime": { "runtime": {
"Wabbajack.Installer.dll": {} "Wabbajack.Installer.dll": {}
} }
}, },
"Wabbajack.IO.Async/0.4.2": { "Wabbajack.IO.Async/0.4.3": {
"runtime": { "runtime": {
"Wabbajack.IO.Async.dll": {} "Wabbajack.IO.Async.dll": {}
} }
}, },
"Wabbajack.Networking.BethesdaNet/0.4.2": { "Wabbajack.Networking.BethesdaNet/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.BethesdaNet.dll": {} "Wabbajack.Networking.BethesdaNet.dll": {}
} }
}, },
"Wabbajack.Networking.Discord/0.4.2": { "Wabbajack.Networking.Discord/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Discord.dll": {} "Wabbajack.Networking.Discord.dll": {}
} }
}, },
"Wabbajack.Networking.GitHub/0.4.2": { "Wabbajack.Networking.GitHub/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0", "Octokit": "14.0.0",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2" "Wabbajack.Networking.Http.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.GitHub.dll": {} "Wabbajack.Networking.GitHub.dll": {}
} }
}, },
"Wabbajack.Networking.Http/0.4.2": { "Wabbajack.Networking.Http/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Http": "9.0.1", "Microsoft.Extensions.Http": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1", "Microsoft.Extensions.Logging": "9.0.1",
"Wabbajack.Configuration": "0.4.2", "Wabbajack.Configuration": "0.4.3",
"Wabbajack.Downloaders.Interfaces": "0.4.2", "Wabbajack.Downloaders.Interfaces": "0.4.3",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2", "Wabbajack.Networking.Http.Interfaces": "0.4.3",
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2" "Wabbajack.Paths.IO": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.dll": {} "Wabbajack.Networking.Http.dll": {}
} }
}, },
"Wabbajack.Networking.Http.Interfaces/0.4.2": { "Wabbajack.Networking.Http.Interfaces/0.4.3": {
"dependencies": { "dependencies": {
"Wabbajack.Hashing.xxHash64": "0.4.2" "Wabbajack.Hashing.xxHash64": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.Http.Interfaces.dll": {} "Wabbajack.Networking.Http.Interfaces.dll": {}
} }
}, },
"Wabbajack.Networking.NexusApi/0.4.2": { "Wabbajack.Networking.NexusApi/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Networking.Http": "0.4.2", "Wabbajack.Networking.Http": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2", "Wabbajack.Networking.Http.Interfaces": "0.4.3",
"Wabbajack.Networking.WabbajackClientApi": "0.4.2" "Wabbajack.Networking.WabbajackClientApi": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.NexusApi.dll": {} "Wabbajack.Networking.NexusApi.dll": {}
} }
}, },
"Wabbajack.Networking.WabbajackClientApi/0.4.2": { "Wabbajack.Networking.WabbajackClientApi/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"Octokit": "14.0.0", "Octokit": "14.0.0",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2", "Wabbajack.Paths.IO": "0.4.3",
"Wabbajack.VFS.Interfaces": "0.4.2", "Wabbajack.VFS.Interfaces": "0.4.3",
"YamlDotNet": "16.3.0" "YamlDotNet": "16.3.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Networking.WabbajackClientApi.dll": {} "Wabbajack.Networking.WabbajackClientApi.dll": {}
} }
}, },
"Wabbajack.Paths/0.4.2": { "Wabbajack.Paths/0.4.3": {
"runtime": { "runtime": {
"Wabbajack.Paths.dll": {} "Wabbajack.Paths.dll": {}
} }
}, },
"Wabbajack.Paths.IO/0.4.2": { "Wabbajack.Paths.IO/0.4.3": {
"dependencies": { "dependencies": {
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"shortid": "4.0.0" "shortid": "4.0.0"
}, },
"runtime": { "runtime": {
"Wabbajack.Paths.IO.dll": {} "Wabbajack.Paths.IO.dll": {}
} }
}, },
"Wabbajack.RateLimiter/0.4.2": { "Wabbajack.RateLimiter/0.4.3": {
"runtime": { "runtime": {
"Wabbajack.RateLimiter.dll": {} "Wabbajack.RateLimiter.dll": {}
} }
}, },
"Wabbajack.Server.Lib/0.4.2": { "Wabbajack.Server.Lib/0.4.3": {
"dependencies": { "dependencies": {
"FluentFTP": "52.0.0", "FluentFTP": "52.0.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
@@ -2264,58 +2264,58 @@
"Nettle": "3.0.0", "Nettle": "3.0.0",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.Networking.Http.Interfaces": "0.4.2", "Wabbajack.Networking.Http.Interfaces": "0.4.3",
"Wabbajack.Services.OSIntegrated": "0.4.2" "Wabbajack.Services.OSIntegrated": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Server.Lib.dll": {} "Wabbajack.Server.Lib.dll": {}
} }
}, },
"Wabbajack.Services.OSIntegrated/0.4.2": { "Wabbajack.Services.OSIntegrated/0.4.3": {
"dependencies": { "dependencies": {
"DeviceId": "6.8.0", "DeviceId": "6.8.0",
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Newtonsoft.Json": "13.0.3", "Newtonsoft.Json": "13.0.3",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"Wabbajack.Compiler": "0.4.2", "Wabbajack.Compiler": "0.4.3",
"Wabbajack.Downloaders.Dispatcher": "0.4.2", "Wabbajack.Downloaders.Dispatcher": "0.4.3",
"Wabbajack.Installer": "0.4.2", "Wabbajack.Installer": "0.4.3",
"Wabbajack.Networking.BethesdaNet": "0.4.2", "Wabbajack.Networking.BethesdaNet": "0.4.3",
"Wabbajack.Networking.Discord": "0.4.2", "Wabbajack.Networking.Discord": "0.4.3",
"Wabbajack.VFS": "0.4.2" "Wabbajack.VFS": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.Services.OSIntegrated.dll": {} "Wabbajack.Services.OSIntegrated.dll": {}
} }
}, },
"Wabbajack.VFS/0.4.2": { "Wabbajack.VFS/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging.Abstractions": "9.0.1", "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
"SixLabors.ImageSharp": "3.1.6", "SixLabors.ImageSharp": "3.1.6",
"System.Data.SQLite.Core": "1.0.119", "System.Data.SQLite.Core": "1.0.119",
"Wabbajack.Common": "0.4.2", "Wabbajack.Common": "0.4.3",
"Wabbajack.FileExtractor": "0.4.2", "Wabbajack.FileExtractor": "0.4.3",
"Wabbajack.Hashing.PHash": "0.4.2", "Wabbajack.Hashing.PHash": "0.4.3",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Paths": "0.4.2", "Wabbajack.Paths": "0.4.3",
"Wabbajack.Paths.IO": "0.4.2", "Wabbajack.Paths.IO": "0.4.3",
"Wabbajack.VFS.Interfaces": "0.4.2" "Wabbajack.VFS.Interfaces": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.dll": {} "Wabbajack.VFS.dll": {}
} }
}, },
"Wabbajack.VFS.Interfaces/0.4.2": { "Wabbajack.VFS.Interfaces/0.4.3": {
"dependencies": { "dependencies": {
"Microsoft.Extensions.DependencyInjection": "9.0.1", "Microsoft.Extensions.DependencyInjection": "9.0.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
"Wabbajack.DTOs": "0.4.2", "Wabbajack.DTOs": "0.4.3",
"Wabbajack.Hashing.xxHash64": "0.4.2", "Wabbajack.Hashing.xxHash64": "0.4.3",
"Wabbajack.Paths": "0.4.2" "Wabbajack.Paths": "0.4.3"
}, },
"runtime": { "runtime": {
"Wabbajack.VFS.Interfaces.dll": {} "Wabbajack.VFS.Interfaces.dll": {}
@@ -2332,7 +2332,7 @@
} }
}, },
"libraries": { "libraries": {
"jackify-engine/0.4.2": { "jackify-engine/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
@@ -3021,202 +3021,202 @@
"path": "yamldotnet/16.3.0", "path": "yamldotnet/16.3.0",
"hashPath": "yamldotnet.16.3.0.nupkg.sha512" "hashPath": "yamldotnet.16.3.0.nupkg.sha512"
}, },
"Wabbajack.CLI.Builder/0.4.2": { "Wabbajack.CLI.Builder/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Common/0.4.2": { "Wabbajack.Common/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compiler/0.4.2": { "Wabbajack.Compiler/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.BSA/0.4.2": { "Wabbajack.Compression.BSA/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Compression.Zip/0.4.2": { "Wabbajack.Compression.Zip/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Configuration/0.4.2": { "Wabbajack.Configuration/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Bethesda/0.4.2": { "Wabbajack.Downloaders.Bethesda/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Dispatcher/0.4.2": { "Wabbajack.Downloaders.Dispatcher/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GameFile/0.4.2": { "Wabbajack.Downloaders.GameFile/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.GoogleDrive/0.4.2": { "Wabbajack.Downloaders.GoogleDrive/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Http/0.4.2": { "Wabbajack.Downloaders.Http/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Interfaces/0.4.2": { "Wabbajack.Downloaders.Interfaces/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.2": { "Wabbajack.Downloaders.IPS4OAuth2Downloader/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Manual/0.4.2": { "Wabbajack.Downloaders.Manual/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.MediaFire/0.4.2": { "Wabbajack.Downloaders.MediaFire/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Mega/0.4.2": { "Wabbajack.Downloaders.Mega/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.ModDB/0.4.2": { "Wabbajack.Downloaders.ModDB/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.Nexus/0.4.2": { "Wabbajack.Downloaders.Nexus/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.VerificationCache/0.4.2": { "Wabbajack.Downloaders.VerificationCache/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Downloaders.WabbajackCDN/0.4.2": { "Wabbajack.Downloaders.WabbajackCDN/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.DTOs/0.4.2": { "Wabbajack.DTOs/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.FileExtractor/0.4.2": { "Wabbajack.FileExtractor/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.PHash/0.4.2": { "Wabbajack.Hashing.PHash/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Hashing.xxHash64/0.4.2": { "Wabbajack.Hashing.xxHash64/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Installer/0.4.2": { "Wabbajack.Installer/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.IO.Async/0.4.2": { "Wabbajack.IO.Async/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.BethesdaNet/0.4.2": { "Wabbajack.Networking.BethesdaNet/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Discord/0.4.2": { "Wabbajack.Networking.Discord/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.GitHub/0.4.2": { "Wabbajack.Networking.GitHub/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http/0.4.2": { "Wabbajack.Networking.Http/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.Http.Interfaces/0.4.2": { "Wabbajack.Networking.Http.Interfaces/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.NexusApi/0.4.2": { "Wabbajack.Networking.NexusApi/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Networking.WabbajackClientApi/0.4.2": { "Wabbajack.Networking.WabbajackClientApi/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths/0.4.2": { "Wabbajack.Paths/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Paths.IO/0.4.2": { "Wabbajack.Paths.IO/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.RateLimiter/0.4.2": { "Wabbajack.RateLimiter/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Server.Lib/0.4.2": { "Wabbajack.Server.Lib/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.Services.OSIntegrated/0.4.2": { "Wabbajack.Services.OSIntegrated/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS/0.4.2": { "Wabbajack.VFS/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""
}, },
"Wabbajack.VFS.Interfaces/0.4.2": { "Wabbajack.VFS.Interfaces/0.4.3": {
"type": "project", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "sha512": ""

Binary file not shown.

View File

@@ -105,7 +105,7 @@ from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton, QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QPushButton,
QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox, QTabWidget, QRadioButton, QButtonGroup QStackedWidget, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QCheckBox, QSpinBox, QMessageBox, QGroupBox, QGridLayout, QFileDialog, QToolButton, QStyle, QComboBox, QTabWidget, QRadioButton, QButtonGroup
) )
from PySide6.QtCore import Qt, QEvent, QTimer from PySide6.QtCore import Qt, QEvent, QTimer, QThread, Signal
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
import json import json
@@ -1533,58 +1533,42 @@ class JackifyMainWindow(QMainWindow):
# Continue anyway - don't block startup on detection errors # Continue anyway - don't block startup on detection errors
def _check_for_updates_on_startup(self): def _check_for_updates_on_startup(self):
"""Check for updates on startup - SIMPLE VERSION""" """Check for updates on startup - non-blocking background check"""
try: try:
debug_print("Checking for updates on startup...") debug_print("Checking for updates on startup...")
# Do it synchronously and simply # Run update check in background thread to avoid blocking GUI startup
class UpdateCheckThread(QThread):
update_available = Signal(object) # Signal to pass update_info to main thread
def __init__(self, update_service):
super().__init__()
self.update_service = update_service
def run(self):
update_info = self.update_service.check_for_updates() update_info = self.update_service.check_for_updates()
if update_info: if update_info:
self.update_available.emit(update_info)
def on_update_available(update_info):
"""Handle update check result in main thread"""
debug_print(f"Update available: v{update_info.version}") debug_print(f"Update available: v{update_info.version}")
# Simple QMessageBox - no complex dialogs # Show update dialog after a short delay to ensure GUI is fully loaded
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import QTimer
def show_update_dialog(): def show_update_dialog():
try: from ..dialogs.update_dialog import UpdateDialog
debug_print("Creating UpdateDialog...")
from jackify.frontends.gui.dialogs.update_dialog import UpdateDialog
dialog = UpdateDialog(update_info, self.update_service, self) dialog = UpdateDialog(update_info, self.update_service, self)
debug_print("UpdateDialog created, showing...") dialog.exec()
dialog.show() # Non-blocking
debug_print("UpdateDialog shown successfully")
except Exception as e:
debug_print(f"UpdateDialog failed: {e}, falling back to simple dialog")
# Fallback to simple dialog
reply = QMessageBox.question(
self,
"Update Available",
f"Jackify v{update_info.version} is available.\n\nDownload and install now?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
# Simple download and replace
try:
new_appimage = self.update_service.download_update(update_info)
if new_appimage:
if self.update_service.apply_update(new_appimage):
debug_print("Update applied successfully")
else:
QMessageBox.warning(self, "Update Failed", "Failed to apply update.")
else:
QMessageBox.warning(self, "Update Failed", "Failed to download update.")
except Exception as e:
QMessageBox.warning(self, "Update Failed", f"Update failed: {e}")
# Use QTimer to show dialog after GUI is fully loaded
QTimer.singleShot(1000, show_update_dialog) QTimer.singleShot(1000, show_update_dialog)
else:
debug_print("No updates available") # Start background thread
self._update_thread = UpdateCheckThread(self.update_service)
self._update_thread.update_available.connect(on_update_available)
self._update_thread.start()
except Exception as e: except Exception as e:
debug_print(f"Error checking for updates on startup: {e}") debug_print(f"Error setting up update check: {e}")
# Continue anyway - don't block startup on update check errors # Continue anyway - don't block startup on update check errors
def cleanup_processes(self): def cleanup_processes(self):

View File

@@ -2157,12 +2157,6 @@ class InstallModlistScreen(QWidget):
cmd.append('--debug') cmd.append('--debug')
debug_print("DEBUG: Added --debug flag to jackify-engine command") debug_print("DEBUG: Added --debug flag to jackify-engine command")
# Check GPU setting and add --no-gpu flag if disabled
gpu_enabled = config_handler.get('enable_gpu_texture_conversion', True)
if not gpu_enabled:
cmd.append('--no-gpu')
debug_print("DEBUG: Added --no-gpu flag (GPU texture conversion disabled)")
# CRITICAL: Print the FULL command so we can see exactly what's being passed # CRITICAL: Print the FULL command so we can see exactly what's being passed
debug_print(f"DEBUG: FULL Engine command: {' '.join(cmd)}") debug_print(f"DEBUG: FULL Engine command: {' '.join(cmd)}")
debug_print(f"DEBUG: modlist value being passed: '{self.modlist}'") debug_print(f"DEBUG: modlist value being passed: '{self.modlist}'")

View File

@@ -388,6 +388,15 @@ class ModlistDetailDialog(QDialog):
main_layout = QVBoxLayout() main_layout = QVBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0) main_layout.setSpacing(0)
# --- Banner area with full-width text overlay ---
# Container so we can place a semi-opaque text panel over the banner image
banner_container = QFrame()
banner_container.setFrameShape(QFrame.NoFrame)
banner_container.setStyleSheet("background: #000; border: none;")
banner_layout = QVBoxLayout()
banner_layout.setContentsMargins(0, 0, 0, 0)
banner_layout.setSpacing(0)
banner_container.setLayout(banner_layout)
# Banner image at top with 16:9 aspect ratio (like Wabbajack) # Banner image at top with 16:9 aspect ratio (like Wabbajack)
self.banner_label = QLabel() self.banner_label = QLabel()
@@ -396,40 +405,67 @@ class ModlistDetailDialog(QDialog):
self.banner_label.setStyleSheet("background: #1a1a1a; border: none;") self.banner_label.setStyleSheet("background: #1a1a1a; border: none;")
self.banner_label.setAlignment(Qt.AlignCenter) self.banner_label.setAlignment(Qt.AlignCenter)
self.banner_label.setText("Loading image...") self.banner_label.setText("Loading image...")
main_layout.addWidget(self.banner_label) banner_layout.addWidget(self.banner_label)
# Content area with padding # Full-width transparent container with opaque card inside (only as wide as text)
overlay_container = QWidget()
overlay_container.setStyleSheet("background: transparent;")
overlay_layout = QHBoxLayout()
overlay_layout.setContentsMargins(24, 0, 24, 24)
overlay_layout.setSpacing(0)
overlay_container.setLayout(overlay_layout)
# Opaque text card - only as wide as content needs (where red lines are)
self.banner_text_panel = QFrame()
self.banner_text_panel.setFrameShape(QFrame.StyledPanel)
# Opaque background, rounded corners, sized to content only
self.banner_text_panel.setStyleSheet("""
QFrame {
background-color: rgba(0, 0, 0, 180);
border: 1px solid rgba(255, 255, 255, 30);
border-radius: 8px;
}
""")
self.banner_text_panel.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
banner_text_layout = QVBoxLayout()
banner_text_layout.setContentsMargins(20, 12, 20, 14)
banner_text_layout.setSpacing(6)
self.banner_text_panel.setLayout(banner_text_layout)
# Add card to container (left-aligned, rest stays transparent)
overlay_layout.addWidget(self.banner_text_panel, alignment=Qt.AlignBottom | Qt.AlignLeft)
overlay_layout.addStretch() # Push card left, rest transparent
# Title only (badges moved to tags section below)
title = QLabel(self.metadata.title)
title.setFont(QFont("Sans", 24, QFont.Bold))
title.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE};")
title.setWordWrap(True)
banner_text_layout.addWidget(title)
# Only sizes in overlay (minimal info on image)
if self.metadata.sizes:
sizes_text = (
f"<span style='color: #aaa;'>Download:</span> {self.metadata.sizes.downloadSizeFormatted}"
f"<span style='color: #aaa;'>Install:</span> {self.metadata.sizes.installSizeFormatted}"
f"<span style='color: #aaa;'>Total:</span> {self.metadata.sizes.totalSizeFormatted}"
)
sizes_label = QLabel(sizes_text)
sizes_label.setStyleSheet("color: #fff; font-size: 13px;")
banner_text_layout.addWidget(sizes_label)
# Add full-width transparent container at bottom of banner
banner_layout.addWidget(overlay_container, alignment=Qt.AlignBottom)
main_layout.addWidget(banner_container)
# Content area with padding (tags + description + bottom bar)
content_widget = QWidget() content_widget = QWidget()
content_layout = QVBoxLayout() content_layout = QVBoxLayout()
content_layout.setContentsMargins(24, 20, 24, 20) content_layout.setContentsMargins(24, 20, 24, 20)
content_layout.setSpacing(16) content_layout.setSpacing(16)
content_widget.setLayout(content_layout) content_widget.setLayout(content_layout)
# Title row with status badges (UNAVAILABLE, Unofficial - Official and NSFW shown in tags) # Metadata line (version, author, game) - moved below image
title_row = QHBoxLayout()
title_row.setSpacing(12)
title = QLabel(self.metadata.title)
title.setFont(QFont("Sans", 24, QFont.Bold))
title.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE};")
title.setWordWrap(True)
title_row.addWidget(title, stretch=1)
# Status badges in title row
if not self.metadata.is_available():
unavailable_badge = QLabel("UNAVAILABLE")
unavailable_badge.setStyleSheet("background: #666; color: white; padding: 4px 10px; font-size: 10px; font-weight: bold; border-radius: 4px;")
title_row.addWidget(unavailable_badge)
# Show "Unofficial" badge if not official (Official is shown in tags)
if not self.metadata.official:
unofficial_badge = QLabel("Unofficial")
unofficial_badge.setStyleSheet("background: #666; color: white; padding: 4px 10px; font-size: 10px; font-weight: bold; border-radius: 4px;")
title_row.addWidget(unofficial_badge)
content_layout.addLayout(title_row)
# Metadata line (version, author, game) - inline like Wabbajack
metadata_line_parts = [] metadata_line_parts = []
if self.metadata.version: if self.metadata.version:
metadata_line_parts.append(f"<span style='color: #aaa;'>version</span> {self.metadata.version}") metadata_line_parts.append(f"<span style='color: #aaa;'>version</span> {self.metadata.version}")
@@ -446,13 +482,25 @@ class ModlistDetailDialog(QDialog):
metadata_line.setWordWrap(True) metadata_line.setWordWrap(True)
content_layout.addWidget(metadata_line) content_layout.addWidget(metadata_line)
# Tags row (like Wabbajack) # Tags row (includes status badges moved from overlay)
tags_to_render = getattr(self.metadata, 'normalized_tags_display', self.metadata.tags or [])
if tags_to_render:
tags_layout = QHBoxLayout() tags_layout = QHBoxLayout()
tags_layout.setSpacing(6) tags_layout.setSpacing(6)
tags_layout.setContentsMargins(0, 0, 0, 0) tags_layout.setContentsMargins(0, 0, 0, 0)
# Add status badges first (UNAVAILABLE, Unofficial)
if not self.metadata.is_available():
unavailable_badge = QLabel("UNAVAILABLE")
unavailable_badge.setStyleSheet("background: #666; color: white; padding: 6px 12px; font-size: 11px; border-radius: 4px;")
tags_layout.addWidget(unavailable_badge)
if not self.metadata.official:
unofficial_badge = QLabel("Unofficial")
unofficial_badge.setStyleSheet("background: #666; color: white; padding: 6px 12px; font-size: 11px; border-radius: 4px;")
tags_layout.addWidget(unofficial_badge)
# Add regular tags
tags_to_render = getattr(self.metadata, 'normalized_tags_display', self.metadata.tags or [])
if tags_to_render:
for tag in tags_to_render: for tag in tags_to_render:
tag_badge = QLabel(tag) tag_badge = QLabel(tag)
# Match Wabbajack tag styling # Match Wabbajack tag styling
@@ -467,17 +515,6 @@ class ModlistDetailDialog(QDialog):
tags_layout.addStretch() tags_layout.addStretch()
content_layout.addLayout(tags_layout) content_layout.addLayout(tags_layout)
# Sizes (if available)
if self.metadata.sizes:
sizes_text = (
f"<span style='color: #aaa;'>Download:</span> {self.metadata.sizes.downloadSizeFormatted}"
f"<span style='color: #aaa;'>Install:</span> {self.metadata.sizes.installSizeFormatted}"
f"<span style='color: #aaa;'>Total:</span> {self.metadata.sizes.totalSizeFormatted}"
)
sizes_label = QLabel(sizes_text)
sizes_label.setStyleSheet("color: #fff; font-size: 13px;")
content_layout.addWidget(sizes_label)
# Description section # Description section
desc_label = QLabel("<b style='color: #aaa; font-size: 14px;'>Description:</b>") desc_label = QLabel("<b style='color: #aaa; font-size: 14px;'>Description:</b>")
content_layout.addWidget(desc_label) content_layout.addWidget(desc_label)
@@ -486,7 +523,8 @@ class ModlistDetailDialog(QDialog):
self.desc_text = QTextEdit() self.desc_text = QTextEdit()
self.desc_text.setReadOnly(True) self.desc_text.setReadOnly(True)
self.desc_text.setPlainText(self.metadata.description or "No description provided.") self.desc_text.setPlainText(self.metadata.description or "No description provided.")
self.desc_text.setFixedHeight(300) # Compact description area; scroll when content is long
self.desc_text.setFixedHeight(120)
self.desc_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.desc_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.desc_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.desc_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.desc_text.setLineWrapMode(QTextEdit.WidgetWidth) self.desc_text.setLineWrapMode(QTextEdit.WidgetWidth)
@@ -832,8 +870,8 @@ class ModlistGalleryDialog(QDialog):
main_layout.addWidget(filter_panel) main_layout.addWidget(filter_panel)
# Right content area (modlist grid) # Right content area (modlist grid)
content_area = self._create_content_area() self.content_area = self._create_content_area()
main_layout.addWidget(content_area, stretch=1) main_layout.addWidget(self.content_area, stretch=1)
self.setLayout(main_layout) self.setLayout(main_layout)
@@ -934,8 +972,8 @@ class ModlistGalleryDialog(QDialog):
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12) layout.setSpacing(12)
# Status label (subtle, top-right) # Status label (subtle, top-right) - hidden during initial loading (popup shows instead)
self.status_label = QLabel("Loading modlists...") self.status_label = QLabel("")
self.status_label.setStyleSheet("color: #888; font-size: 10px;") self.status_label.setStyleSheet("color: #888; font-size: 10px;")
self.status_label.setAlignment(Qt.AlignRight | Qt.AlignTop) self.status_label.setAlignment(Qt.AlignRight | Qt.AlignTop)
layout.addWidget(self.status_label) layout.addWidget(self.status_label)
@@ -965,13 +1003,55 @@ class ModlistGalleryDialog(QDialog):
from PySide6.QtCore import QThread, Signal from PySide6.QtCore import QThread, Signal
from PySide6.QtGui import QFont from PySide6.QtGui import QFont
# Make status label more prominent during loading # Hide status label during loading (popup dialog will show instead)
self.status_label.setText("Loading modlists...") self.status_label.setVisible(False)
# Show loading overlay directly in content area (simpler than separate dialog)
self._loading_overlay = QWidget(self.content_area)
self._loading_overlay.setStyleSheet("""
QWidget {
background-color: rgba(35, 35, 35, 240);
border-radius: 8px;
}
""")
overlay_layout = QVBoxLayout()
overlay_layout.setContentsMargins(30, 20, 30, 20)
overlay_layout.setSpacing(12)
self._loading_label = QLabel("Loading modlists")
self._loading_label.setAlignment(Qt.AlignCenter)
# Set fixed width to prevent text shifting when dots animate
# Width accommodates "Loading modlists..." (longest version)
self._loading_label.setFixedWidth(220)
font = QFont() font = QFont()
font.setPointSize(14) font.setPointSize(14)
font.setBold(True) font.setBold(True)
self.status_label.setFont(font) self._loading_label.setFont(font)
self.status_label.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 14px; font-weight: bold;") self._loading_label.setStyleSheet(f"color: {JACKIFY_COLOR_BLUE}; font-size: 14px; font-weight: bold;")
overlay_layout.addWidget(self._loading_label)
self._loading_overlay.setLayout(overlay_layout)
self._loading_overlay.setFixedSize(300, 120)
# Animate dots in loading message
self._loading_dot_count = 0
self._loading_dot_timer = QTimer()
self._loading_dot_timer.timeout.connect(self._animate_loading_dots)
self._loading_dot_timer.start(500) # Update every 500ms
# Position overlay in center of content area
def position_overlay():
if hasattr(self, 'content_area') and self.content_area.isVisible():
content_width = self.content_area.width()
content_height = self.content_area.height()
x = (content_width - 300) // 2
y = (content_height - 120) // 2
self._loading_overlay.move(x, y)
self._loading_overlay.show()
self._loading_overlay.raise_()
# Delay slightly to ensure content_area is laid out
QTimer.singleShot(50, position_overlay)
class ModlistLoaderThread(QThread): class ModlistLoaderThread(QThread):
"""Background thread to load modlist metadata""" """Background thread to load modlist metadata"""
@@ -987,9 +1067,10 @@ class ModlistGalleryDialog(QDialog):
start_time = time.time() start_time = time.time()
# Fetch metadata (CPU-intensive work happens here in background) # Fetch metadata (CPU-intensive work happens here in background)
# Skip search index initially for faster loading - can be loaded later if user searches
metadata_response = self.gallery_service.fetch_modlist_metadata( metadata_response = self.gallery_service.fetch_modlist_metadata(
include_validation=False, include_validation=False,
include_search_index=True, include_search_index=False, # Skip for faster initial load
sort_by="title" sort_by="title"
) )
@@ -1010,17 +1091,31 @@ class ModlistGalleryDialog(QDialog):
self._loader_thread.finished.connect(self._on_modlists_loaded) self._loader_thread.finished.connect(self._on_modlists_loaded)
self._loader_thread.start() self._loader_thread.start()
def _animate_loading_dots(self):
"""Animate dots in loading message"""
if hasattr(self, '_loading_label') and self._loading_label:
self._loading_dot_count = (self._loading_dot_count + 1) % 4
dots = "." * self._loading_dot_count
# Pad with spaces to keep text width constant (prevents shifting)
padding = " " * (3 - self._loading_dot_count)
self._loading_label.setText(f"Loading modlists{dots}{padding}")
def _on_modlists_loaded(self, metadata_response, error_message): def _on_modlists_loaded(self, metadata_response, error_message):
"""Handle modlist metadata loaded in background thread (runs in GUI thread)""" """Handle modlist metadata loaded in background thread (runs in GUI thread)"""
import random import random
from PySide6.QtCore import QTimer
from PySide6.QtGui import QFont from PySide6.QtGui import QFont
# Restore normal status label styling # Stop animation timer and close loading overlay
font = QFont() if hasattr(self, '_loading_dot_timer') and self._loading_dot_timer:
font.setPointSize(10) self._loading_dot_timer.stop()
self.status_label.setFont(font) self._loading_dot_timer = None
self.status_label.setStyleSheet("color: #888; font-size: 10px;")
if hasattr(self, '_loading_overlay') and self._loading_overlay:
self._loading_overlay.hide()
self._loading_overlay.deleteLater()
self._loading_overlay = None
self.status_label.setVisible(True)
if error_message: if error_message:
self.status_label.setText(f"Error loading modlists: {error_message}") self.status_label.setText(f"Error loading modlists: {error_message}")