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
+88
-4
+88
-4
@@ -494,11 +494,90 @@ def _remove_readonly(func: Callable[[str], Any], path: str, exc: Any) -> None:
|
|||||||
func(path)
|
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:
|
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.
|
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.
|
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.
|
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:
|
if not finish_update:
|
||||||
@@ -670,6 +749,12 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
|
|||||||
primary_update_folder = update_folders[0]
|
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
|
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
|
# 3. Wait for updater process to exit
|
||||||
print(f"Waiting for {app_name}_updater to exit...")
|
print(f"Waiting for {app_name}_updater to exit...")
|
||||||
updater_bin_name = f"{app_name}_updater"
|
updater_bin_name = f"{app_name}_updater"
|
||||||
@@ -739,8 +824,7 @@ def finish_update_if_needed(platform_name: str, app_name: str, cfg_path: str, fi
|
|||||||
else:
|
else:
|
||||||
print(f"Failed to delete update folder '{folder}' after 3 attempts: {e}")
|
print(f"Failed to delete update folder '{folder}' after 3 attempts: {e}")
|
||||||
|
|
||||||
# 6. Restore plugins after update (Issue #113)
|
# Note: _restore_plugins() no longer needed - selective update preserves plugins automatically
|
||||||
_restore_plugins(platform_name, app_name, target_base)
|
|
||||||
|
|
||||||
if "--finish-update" in sys.argv:
|
if "--finish-update" in sys.argv:
|
||||||
sys.argv.remove("--finish-update")
|
sys.argv.remove("--finish-update")
|
||||||
|
|||||||
Reference in new issue
Block a user