Merge branch 'main' of https://git.research.dezeeuw.ca/hermes/flares
Test Suite / test (push) Canceled after 0s

This commit is contained in:
tyler committed 2026-11-07 17:41:30 -07:00
commit 8c432f3da4
1 file changed
+89 -5
+89 -5
View File
@@ -494,11 +494,90 @@ def _remove_readonly(func: Callable[[str], Any], path: str, exc: Any) -> None:
func(path)
def _smart_selective_update(extract_path: str, target_dir: str) -> tuple[int, int]:
"""
Performs selective file replacement by comparing modification times.
Issue #113: Smart update that only replaces changed files
Args:
extract_path: Path to extracted new version files
target_dir: Target application directory to update
Returns:
Tuple of (files_updated, files_skipped)
Notes:
- Skips plugins/ directory entirely to preserve user plugins
- Only copies files that are newer in extract_path than target_dir
- Creates missing directories as needed
- Uses atomic file operations for safety
"""
import stat
files_updated = 0
files_skipped = 0
# Directories to skip (preserve user data)
skip_dirs = {'plugins', '__pycache__', '.git', 'node_modules'}
def should_skip_dir(dirname):
return dirname in skip_dirs or dirname.startswith('.')
def should_copy_file(src_path, dst_path):
"""Return True if file should be copied (newer or doesn't exist)"""
if not os.path.exists(dst_path):
return True
src_mtime = os.path.getmtime(src_path)
dst_mtime = os.path.getmtime(dst_path)
# Copy if source is newer by at least 1 second (avoid rounding issues)
return src_mtime > dst_mtime + 1
# Walk through extracted files
for root, dirs, files in os.walk(extract_path):
# Filter out directories to skip
dirs[:] = [d for d in dirs if not should_skip_dir(d)]
for filename in files:
src_path = os.path.join(root, filename)
# Calculate relative path and destination
rel_path = os.path.relpath(src_path, extract_path)
dst_path = os.path.join(target_dir, rel_path)
# Create destination directory if needed
dst_dir = os.path.dirname(dst_path)
os.makedirs(dst_dir, exist_ok=True)
# Check if file needs updating
if should_copy_file(src_path, dst_path):
try:
# Atomic copy with metadata preservation
shutil.copy2(src_path, dst_path + '.tmp')
os.rename(dst_path + '.tmp', dst_path) # Atomic rename
# Preserve permissions
src_mode = os.stat(src_path).st_mode & stat.S_IWRITE
if src_mode:
try:
os.chmod(dst_path, os.stat(src_path).st_mode)
except Exception as e:
print(f"Warning: Could not set permissions on {dst_path}: {e}")
files_updated += 1
except Exception as e:
print(f"Failed to copy {rel_path}: {e}")
else:
files_skipped += 1
return files_updated, files_skipped
def _backup_plugins(platform_name: str, app_name: str, source_dir: str, dest_dir: Optional[str] = None) -> bool:
"""
Backs up plugins from source directory to a temporary location.
Issue #113: Preserve plugins during updates
Issue #113: Preserve plugins during updates (FALLBACK METHOD - rarely used now)
Returns True if backup was successful, False otherwise.
"""
@@ -619,7 +698,7 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
"""
Completes a pending application update if '--finish-update' is present in the command-line arguments.
Issue #113: Added plugin preservation during updates.
Issue #113: Added smart selective update (only replaces changed files).
"""
if not finish_update:
@@ -669,6 +748,12 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
primary_update_folder = update_folders[0]
target_updater_folder = os.path.join(primary_update_folder, f"{app_name}-darwin") if platform_name == 'darwin' else primary_update_folder
# 2a. Perform SMART SELECTIVE UPDATE (Issue #113)
target_base = get_app_dir()
print(f"Performing selective update from: {primary_update_folder}")
files_updated, files_skipped = _smart_selective_update(primary_update_folder, target_base)
print(f"Selective update complete: {files_updated} files updated, {files_skipped} files skipped")
# 3. Wait for updater process to exit
print(f"Waiting for {app_name}_updater to exit...")
@@ -739,9 +824,8 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
else:
print(f"Failed to delete update folder '{folder}' after 3 attempts: {e}")
# 6. Restore plugins after update (Issue #113)
_restore_plugins(platform_name, app_name, target_base)
# Note: _restore_plugins() no longer needed - selective update preserves plugins automatically
if "--finish-update" in sys.argv:
sys.argv.remove("--finish-update")