Sync from development - prepare for v0.1.1

This commit is contained in:
Omni
2025-09-15 20:18:13 +01:00
parent 0b6e32beac
commit 70b18004e1
64 changed files with 5142 additions and 1164 deletions

View File

@@ -101,7 +101,7 @@ class AutomatedPrefixService:
logger.info(f" Native Steam service created shortcut successfully with AppID: {app_id}")
return True, app_id
else:
logger.error("Native Steam service failed to create shortcut")
logger.error("Native Steam service failed to create shortcut")
return False, None
except Exception as e:
@@ -471,7 +471,7 @@ exit"""
logger.warning(f"Error running protontricks -l on attempt {i+1}: {e}")
time.sleep(1)
logger.error(f"Shortcut '{shortcut_name}' not found in protontricks after 30 seconds")
logger.error(f"Shortcut '{shortcut_name}' not found in protontricks after 30 seconds")
return None
except Exception as e:
@@ -939,7 +939,7 @@ echo Prefix creation complete.
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired):
continue
logger.info(" No more processes to kill")
logger.info("No more processes to kill")
return True
except Exception as e:
@@ -1296,7 +1296,7 @@ echo Prefix creation complete.
time.sleep(1)
logger.warning(f"Timeout waiting for prefix completion after {timeout} seconds")
logger.warning(f"Timeout waiting for prefix completion after {timeout} seconds")
return False
except Exception as e:
@@ -1356,7 +1356,7 @@ echo Prefix creation complete.
if killed_count > 0:
logger.info(f" Killed {killed_count} ModOrganizer processes")
else:
logger.warning("No ModOrganizer processes found to kill")
logger.warning("No ModOrganizer processes found to kill")
return killed_count
@@ -1624,11 +1624,11 @@ echo Prefix creation complete.
return True
logger.error(f"Shortcut '{shortcut_name}' not found for CompatTool setting")
logger.error(f"Shortcut '{shortcut_name}' not found for CompatTool setting")
return False
except Exception as e:
logger.error(f"Error setting CompatTool on shortcut: {e}")
logger.error(f"Error setting CompatTool on shortcut: {e}")
return False
def _set_proton_on_shortcut(self, shortcut_name: str) -> bool:
@@ -2633,7 +2633,7 @@ echo Prefix creation complete.
logger.info(f" Proton prefix created at: {pfx}")
return True
else:
logger.warning(f"⚠️ Proton prefix not found at: {pfx}")
logger.warning(f"Proton prefix not found at: {pfx}")
return False
except subprocess.TimeoutExpired:
@@ -2735,7 +2735,7 @@ echo Prefix creation complete.
logger.info(" Compatibility tool persists")
return True
else:
logger.warning("⚠️ Compatibility tool not found")
logger.warning("Compatibility tool not found")
return False
except Exception as e:

View File

@@ -228,14 +228,14 @@ class NativeSteamService:
# Write back to file
if self.write_shortcuts_vdf(data):
logger.info(f"Shortcut created successfully at index {next_index}")
logger.info(f"Shortcut created successfully at index {next_index}")
return True, unsigned_app_id
else:
logger.error("Failed to write shortcut to VDF")
logger.error("Failed to write shortcut to VDF")
return False, None
except Exception as e:
logger.error(f"Error creating shortcut: {e}")
logger.error(f"Error creating shortcut: {e}")
return False, None
def set_proton_version(self, app_id: int, proton_version: str = "proton_experimental") -> bool:
@@ -320,11 +320,11 @@ class NativeSteamService:
with open(config_path, 'w', encoding='utf-8') as f:
f.write(new_config_text)
logger.info(f"Successfully set Proton version '{proton_version}' for AppID {app_id} using config.vdf only (steam-conductor method)")
logger.info(f"Successfully set Proton version '{proton_version}' for AppID {app_id} using config.vdf only (steam-conductor method)")
return True
except Exception as e:
logger.error(f"Error setting Proton version: {e}")
logger.error(f"Error setting Proton version: {e}")
return False
def create_shortcut_with_proton(self, app_name: str, exe_path: str, start_dir: str = None,
@@ -351,7 +351,7 @@ class NativeSteamService:
logger.error("Failed to set Proton version (shortcut still created)")
return False, app_id # Shortcut exists but Proton setting failed
logger.info(f"Complete workflow successful: '{app_name}' with '{proton_version}'")
logger.info(f"Complete workflow successful: '{app_name}' with '{proton_version}'")
return True, app_id
def list_shortcuts(self) -> Dict[str, str]:
@@ -388,12 +388,12 @@ class NativeSteamService:
# Write back
if self.write_shortcuts_vdf(data):
logger.info(f"Removed shortcut '{app_name}'")
logger.info(f"Removed shortcut '{app_name}'")
return True
else:
logger.error("Failed to write updated shortcuts")
logger.error("Failed to write updated shortcuts")
return False
except Exception as e:
logger.error(f"Error removing shortcut: {e}")
logger.error(f"Error removing shortcut: {e}")
return False

View File

@@ -33,6 +33,7 @@ class UpdateInfo:
download_url: str
file_size: Optional[int] = None
is_critical: bool = False
is_delta_update: bool = False
class UpdateService:
@@ -72,24 +73,44 @@ class UpdateService:
latest_version = release_data['tag_name'].lstrip('v')
if self._is_newer_version(latest_version):
# Find AppImage asset
# Check if this version was skipped
if self._is_version_skipped(latest_version):
logger.debug(f"Version {latest_version} was skipped by user")
return None
# Find AppImage asset (prefer delta update if available)
download_url = None
file_size = None
# Look for delta update first (smaller download)
for asset in release_data.get('assets', []):
if asset['name'].endswith('.AppImage'):
if asset['name'].endswith('.AppImage.delta') or 'delta' in asset['name'].lower():
download_url = asset['browser_download_url']
file_size = asset['size']
logger.debug(f"Found delta update: {asset['name']} ({file_size} bytes)")
break
# Fallback to full AppImage if no delta available
if not download_url:
for asset in release_data.get('assets', []):
if asset['name'].endswith('.AppImage'):
download_url = asset['browser_download_url']
file_size = asset['size']
logger.debug(f"Found full AppImage: {asset['name']} ({file_size} bytes)")
break
if download_url:
# Determine if this is a delta update
is_delta = '.delta' in download_url or 'delta' in download_url.lower()
return UpdateInfo(
version=latest_version,
tag_name=release_data['tag_name'],
release_date=release_data['published_at'],
changelog=release_data.get('body', ''),
download_url=download_url,
file_size=file_size
file_size=file_size,
is_delta_update=is_delta
)
else:
logger.warning(f"No AppImage found in release {latest_version}")
@@ -123,6 +144,25 @@ class UpdateService:
logger.warning(f"Could not parse version: {version}")
return False
def _is_version_skipped(self, version: str) -> bool:
"""
Check if a version was skipped by the user.
Args:
version: Version to check
Returns:
bool: True if version was skipped, False otherwise
"""
try:
from ...backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()
skipped_versions = config_handler.get('skipped_versions', [])
return version in skipped_versions
except Exception as e:
logger.warning(f"Error checking skipped versions: {e}")
return False
def check_for_updates_async(self, callback: Callable[[Optional[UpdateInfo]], None]) -> None:
"""
Check for updates in background thread.
@@ -152,16 +192,25 @@ class UpdateService:
logger.debug("Not running as AppImage - updates not supported")
return False
appimage_path = get_appimage_path()
if not appimage_path:
logger.debug("AppImage path validation failed - updates not supported")
return False
if not can_self_update():
logger.debug("Cannot write to AppImage - updates not possible")
return False
logger.debug(f"Self-updating enabled for AppImage: {appimage_path}")
return True
def download_update(self, update_info: UpdateInfo,
progress_callback: Optional[Callable[[int, int], None]] = None) -> Optional[Path]:
"""
Download update to temporary location.
Download update using full AppImage replacement.
Since we can't rely on external tools being available, we use a reliable
full replacement approach that works on all systems without dependencies.
Args:
update_info: Information about the update to download
@@ -171,7 +220,27 @@ class UpdateService:
Path to downloaded file, or None if download failed
"""
try:
logger.info(f"Downloading update {update_info.version} from {update_info.download_url}")
logger.info(f"Downloading update {update_info.version} (full replacement)")
return self._download_update_manual(update_info, progress_callback)
except Exception as e:
logger.error(f"Failed to download update: {e}")
return None
def _download_update_manual(self, update_info: UpdateInfo,
progress_callback: Optional[Callable[[int, int], None]] = None) -> Optional[Path]:
"""
Fallback manual download method.
Args:
update_info: Information about the update to download
progress_callback: Optional callback for download progress
Returns:
Path to downloaded file, or None if download failed
"""
try:
logger.info(f"Manual download of update {update_info.version} from {update_info.download_url}")
response = requests.get(update_info.download_url, stream=True)
response.raise_for_status()
@@ -179,11 +248,12 @@ class UpdateService:
total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0
# Create temporary file
temp_dir = Path(tempfile.gettempdir()) / "jackify_updates"
temp_dir.mkdir(exist_ok=True)
# Create update directory in user's home directory
home_dir = Path.home()
update_dir = home_dir / "Jackify" / "updates"
update_dir.mkdir(parents=True, exist_ok=True)
temp_file = temp_dir / f"Jackify-{update_info.version}.AppImage"
temp_file = update_dir / f"Jackify-{update_info.version}.AppImage"
with open(temp_file, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
@@ -197,11 +267,11 @@ class UpdateService:
# Make executable
temp_file.chmod(0o755)
logger.info(f"Update downloaded successfully to {temp_file}")
logger.info(f"Manual update downloaded successfully to {temp_file}")
return temp_file
except Exception as e:
logger.error(f"Failed to download update: {e}")
logger.error(f"Failed to download update manually: {e}")
return None
def apply_update(self, new_appimage_path: Path) -> bool:
@@ -252,10 +322,12 @@ class UpdateService:
Path to helper script, or None if creation failed
"""
try:
temp_dir = Path(tempfile.gettempdir()) / "jackify_updates"
temp_dir.mkdir(exist_ok=True)
# Create update directory in user's home directory
home_dir = Path.home()
update_dir = home_dir / "Jackify" / "updates"
update_dir.mkdir(parents=True, exist_ok=True)
helper_script = temp_dir / "update_helper.sh"
helper_script = update_dir / "update_helper.sh"
script_content = f'''#!/bin/bash
# Jackify Update Helper Script