fix(ui, calculation, state-management): Improve disk usage display and fix race
conditions This commit addresses several issues related to the disk usage display in both backup and restore modes, and resolves race conditions during mode switches. **Corrected restore mode projection calculation:** Introduced dedicated state variables (`restore_destination_folder_size_bytes`, `restore_source_size_bytes`) to accurately calculate and display projected disk usage and size differences in restore mode, preventing state pollution from other modes. **Ensured correct disk display on mode switch:** Implemented logic to re-evaluate and set `destination_total_bytes` and `destination_used_bytes` based on the currently active mode's selected path. This prevents stale disk information from one mode being displayed in another. **Fixed race conditions during background calculations:** Implemented a robust cancellation mechanism for all background folder size calculations. Threads now pass their originating mode, and results are discarded if the application's mode has changed, preventing UI state corruption. This includes managing both left and right canvas calculation threads. **feat(ui): Added explicit UI reset on action:** Introduced a `reset_projection_canvases` function to clear all projection displays and reset relevant data variables immediately when a new action (e.g., folder selection, mode switch) is initiated. This provides a cleaner user experience by preventing stale data from being shown while new calculations are in progress. **Resolved missing imports:** Added necessary `import os` and `import shutil` statements to `pyimage_ui/navigation.py` to resolve runtime errors.
This commit is contained in:
@@ -84,6 +84,8 @@ class MainApplication(tk.Tk):
|
||||
|
||||
self.left_canvas_animation = None
|
||||
self.right_canvas_animation = None
|
||||
self.right_calculation_thread = None
|
||||
self.right_calculation_stop_event = None
|
||||
self.destination_path = None
|
||||
self.source_size_bytes = 0
|
||||
self.destination_used_bytes = 0
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -160,6 +160,9 @@ class Drawing:
|
||||
self.redraw_left_canvas()
|
||||
|
||||
def calculate_restore_folder_size(self):
|
||||
if self.app.right_calculation_thread and self.app.right_calculation_thread.is_alive():
|
||||
self.app.right_calculation_stop_event.set()
|
||||
|
||||
self.app.start_pause_button.config(state="disabled")
|
||||
path_to_calculate = self.app.right_canvas_data.get('path_display')
|
||||
if path_to_calculate and os.path.isdir(path_to_calculate):
|
||||
@@ -167,35 +170,50 @@ class Drawing:
|
||||
self.app.right_canvas_data['size'] = Msg.STR['calculating_size']
|
||||
self._start_calculating_animation(self.app.right_canvas)
|
||||
self.redraw_right_canvas_restore()
|
||||
threading.Thread(target=self._calculate_and_update_restore_size, args=(path_to_calculate,), daemon=True).start()
|
||||
|
||||
def _calculate_and_update_restore_size(self, path):
|
||||
self.app.right_calculation_stop_event = threading.Event()
|
||||
self.app.right_calculation_thread = threading.Thread(target=self._calculate_and_update_restore_size, args=(
|
||||
path_to_calculate, self.app.right_calculation_stop_event), daemon=True)
|
||||
self.app.right_calculation_thread.start()
|
||||
|
||||
def _calculate_and_update_restore_size(self, path, stop_event):
|
||||
total_size = 0
|
||||
try:
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
if stop_event.is_set():
|
||||
return
|
||||
for f in filenames:
|
||||
if stop_event.is_set():
|
||||
return
|
||||
fp = os.path.join(dirpath, f)
|
||||
if not os.path.islink(fp):
|
||||
total_size += os.path.getsize(fp)
|
||||
try:
|
||||
total_size += os.path.getsize(fp)
|
||||
except OSError:
|
||||
pass # Ignore files that can't be accessed
|
||||
except OSError as e:
|
||||
print(f"Error calculating size for {path}: {e}")
|
||||
size_text = "Error"
|
||||
else:
|
||||
if stop_event.is_set():
|
||||
return
|
||||
size_gb = total_size / (1024**3)
|
||||
size_text = f"{size_gb:.2f} GB"
|
||||
self.app.restore_source_size_bytes = total_size
|
||||
|
||||
def update_ui():
|
||||
if stop_event.is_set():
|
||||
return
|
||||
self.app.right_canvas_data['size'] = size_text
|
||||
self.app.right_canvas_data['calculating'] = False
|
||||
self._stop_calculating_animation(self.app.right_canvas)
|
||||
self.redraw_right_canvas_restore()
|
||||
|
||||
# Only enable the button if the OTHER calculation is not running.
|
||||
if not self.app.left_canvas_data.get('calculating', False):
|
||||
self.app.start_pause_button.config(state="normal")
|
||||
|
||||
self.app.after(0, update_ui)
|
||||
if not stop_event.is_set():
|
||||
self.app.after(0, update_ui)
|
||||
|
||||
def update_target_projection(self):
|
||||
if self.app.mode == "restore":
|
||||
|
@@ -1,4 +1,6 @@
|
||||
# pyimage_ui/navigation.py
|
||||
import os
|
||||
import shutil
|
||||
from shared_libs.message import MessageDialog
|
||||
from app_config import Msg
|
||||
|
||||
@@ -10,6 +12,8 @@ class Navigation:
|
||||
def _cancel_calculation(self):
|
||||
if self.app.calculation_thread and self.app.calculation_thread.is_alive():
|
||||
self.app.calculation_stop_event.set()
|
||||
if self.app.right_calculation_thread and self.app.right_calculation_thread.is_alive():
|
||||
self.app.right_calculation_stop_event.set()
|
||||
|
||||
# Stop all calculation animations and update UI
|
||||
self.app.drawing._stop_calculating_animation() # Stops both left and right
|
||||
@@ -25,16 +29,44 @@ class Navigation:
|
||||
self.app.mode = mode
|
||||
self._cancel_calculation()
|
||||
|
||||
# Point to the correct data dictionaries for the mode
|
||||
if mode == "backup":
|
||||
self.app.left_canvas_data = self.app.backup_left_canvas_data
|
||||
self.app.right_canvas_data = self.app.backup_right_canvas_data
|
||||
active_index = 0
|
||||
|
||||
# Restore backup destination disk metrics
|
||||
backup_dest_path = self.app.backup_right_canvas_data.get('path_display')
|
||||
if backup_dest_path and os.path.isdir(backup_dest_path):
|
||||
try:
|
||||
total, used, free = shutil.disk_usage(backup_dest_path)
|
||||
self.app.destination_total_bytes = total
|
||||
self.app.destination_used_bytes = used
|
||||
except FileNotFoundError:
|
||||
self.app.destination_total_bytes = 0
|
||||
self.app.destination_used_bytes = 0
|
||||
else:
|
||||
self.app.destination_total_bytes = 0
|
||||
self.app.destination_used_bytes = 0
|
||||
|
||||
else: # restore
|
||||
self.app.left_canvas_data = self.app.restore_left_canvas_data
|
||||
self.app.right_canvas_data = self.app.restore_right_canvas_data
|
||||
active_index = 1
|
||||
|
||||
# Restore restore destination disk metrics
|
||||
restore_dest_path = self.app.restore_left_canvas_data.get('path_display')
|
||||
if restore_dest_path and os.path.isdir(restore_dest_path):
|
||||
try:
|
||||
total, used, free = shutil.disk_usage(restore_dest_path)
|
||||
self.app.destination_total_bytes = total
|
||||
self.app.destination_used_bytes = used
|
||||
except FileNotFoundError:
|
||||
self.app.destination_total_bytes = 0
|
||||
self.app.destination_used_bytes = 0
|
||||
else:
|
||||
self.app.destination_total_bytes = 0
|
||||
self.app.destination_used_bytes = 0
|
||||
|
||||
# Set default content if the dictionaries are empty
|
||||
if not self.app.left_canvas_data:
|
||||
if mode == "backup":
|
||||
|
Reference in New Issue
Block a user