Merge branch 'main' of https://git.research.dezeeuw.ca/hermes/flares
Test Suite / test (push) Canceled after 0s
Test Suite / test (push) Canceled after 0s
This commit is contained in:
commit
8c432f3da4
1 file changed
+89
-5
+89
-5
@@ -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")
|
||||
|
||||
|
||||
Reference in new issue
Block a user