From 16b8a92a243fd9e25a29e0f9e6f8032abcc4a8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9sir=C3=A9=20Werner=20Menrath?= Date: Mon, 1 Sep 2025 19:10:15 +0200 Subject: [PATCH] fix: Improve backup detection and UI updates - Ensure backup content view updates after backup completion or deletion. - Fix "Please wait" animation not stopping after backup deletion. - Refactor backup type selection logic for more reliable UI updates. - Correct backup detection after resetting settings. - Ensure correct size is written to info file for all backup types. --- backup_manager.py | 2 +- main_app.py | 2 + pbp_app_config.py | 2 + pyimage_ui/actions.py | 79 +++++++++++++-------------- pyimage_ui/advanced_settings_frame.py | 59 +++++++++++++++++++- 5 files changed, 101 insertions(+), 43 deletions(-) diff --git a/backup_manager.py b/backup_manager.py index 5d196bd..64d9c74 100644 --- a/backup_manager.py +++ b/backup_manager.py @@ -299,7 +299,7 @@ set -e if status in ['success', 'warning'] and not is_dry_run: info_filename_base = os.path.basename(dest_path) - final_size = transferred_size if latest_backup_path else source_size + final_size = transferred_size if is_compressed: self.logger.log(f"Compression requested for {dest_path}") diff --git a/main_app.py b/main_app.py index aa1432f..f1c66ce 100644 --- a/main_app.py +++ b/main_app.py @@ -577,6 +577,7 @@ class MainApplication(tk.Tk): elif message_type == 'deletion_complete': self.backup_content_frame.hide_deletion_status() self.backup_content_frame.system_backups_frame._load_backup_content() + self.backup_content_frame.user_backups_frame._load_backup_content() elif message_type == 'error': self.animated_icon.stop("DISABLE") self.start_pause_button["text"] = "Start" @@ -622,6 +623,7 @@ class MainApplication(tk.Tk): self.backup_is_running = False self.actions._set_ui_state(True) + self.backup_content_frame.system_backups_frame._load_backup_content() elif message_type == 'completion_accurate': if value == 'success': self.info_label.config( diff --git a/pbp_app_config.py b/pbp_app_config.py index fcf8ec4..32595a0 100644 --- a/pbp_app_config.py +++ b/pbp_app_config.py @@ -273,6 +273,8 @@ class Msg: "exclude_dialog_text": _("Do you want to add a folder or a file?"), "add_folder_button": _("Folder"), "add_file_button": _("File"), + "system_excludes": _("System Excludes"), + "manual_excludes": _("Manual Excludes"), # Menus "file_menu": _("File"), diff --git a/pyimage_ui/actions.py b/pyimage_ui/actions.py index c3bbe0f..96697cf 100644 --- a/pyimage_ui/actions.py +++ b/pyimage_ui/actions.py @@ -18,6 +18,20 @@ class Actions: def __init__(self, app): self.app = app + def _set_backup_type(self, backup_type: str): + if backup_type == "full": + self.app.vollbackup_var.set(True) + self.app.inkrementell_var.set(False) + self.app.full_backup_cb.config(state="disabled") + self.app.incremental_cb.config(state="disabled") + self.app.accurate_size_cb.config(state="disabled") + elif backup_type == "incremental": + self.app.vollbackup_var.set(False) + self.app.inkrementell_var.set(True) + self.app.full_backup_cb.config(state="normal") + self.app.incremental_cb.config(state="normal") + self.app.accurate_size_cb.config(state="normal") + def _update_backup_type_controls(self): """ Updates the state of the Full/Incremental backup radio buttons based on @@ -46,43 +60,17 @@ class Actions: break if full_backup_exists: - # Case 1: A full backup exists. Allow user to choose, default to incremental. - if not self.app.vollbackup_var.get(): # If user has not manually selected full backup - self.app.vollbackup_var.set(False) - self.app.inkrementell_var.set(True) - self.app.full_backup_cb.config(state="normal") - self.app.incremental_cb.config(state="normal") - self.app.accurate_size_cb.config( - state="normal") # Enable accurate calc + self._set_backup_type("incremental") else: - # Case 2: No full backup exists. Force a full backup. - self.app.vollbackup_var.set(True) - self.app.inkrementell_var.set(False) - self.app.full_backup_cb.config(state="disabled") - self.app.incremental_cb.config(state="disabled") - self.app.accurate_size_cb.config( - state="disabled") # Disable accurate calc + self._set_backup_type("full") def handle_backup_type_change(self, changed_var_name): - # This function is called when the user clicks on "Full" or "Incremental". - # It enforces that only one can be selected and updates the accurate size checkbox accordingly. if changed_var_name == 'voll': - if self.app.vollbackup_var.get(): # if "Full" was checked - self.app.inkrementell_var.set(False) + if self.app.vollbackup_var.get(): + self._set_backup_type("full") elif changed_var_name == 'inkrementell': - if self.app.inkrementell_var.get(): # if "Incremental" was checked - self.app.vollbackup_var.set(False) - - # Now, update the state of the accurate calculation checkbox - if self.app.vollbackup_var.get(): - self.app.accurate_size_cb.config(state="disabled") - self.app.genaue_berechnung_var.set(False) - else: - # Only enable if it's an incremental backup of the system - if self.app.left_canvas_data.get('folder') == "Computer": - self.app.accurate_size_cb.config(state="normal") - else: - self.app.accurate_size_cb.config(state="disabled") + if self.app.inkrementell_var.get(): + self._set_backup_type("incremental") def handle_compression_change(self): if self.app.compressed_var.get(): @@ -349,6 +337,8 @@ class Actions: def _update_right_canvas_info(self, path): try: if self.app.mode == "backup": + self.app.destination_path = path + backup_root_to_exclude = f"/{path.strip('/').split('/')[0]}" try: with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'r+') as f: @@ -364,13 +354,12 @@ class Actions: app_logger.log(f"Error updating exclusion list: {e}") total, used, free = shutil.disk_usage(path) - self.app.destination_path = path self.app.destination_total_bytes = total self.app.destination_used_bytes = used size_str = f"{used / (1024**3):.2f} GB / {total / (1024**3):.2f} GB" self.app.right_canvas_data.update({ - 'folder': os.path.basename(path.rstrip('/')), + 'folder': os.path.basename(path.rstrip('/')), 'path_display': path, 'size': size_str }) @@ -386,7 +375,7 @@ class Actions: elif self.app.mode == "restore": self.app.right_canvas_data.update({ - 'folder': os.path.basename(path.rstrip('/')), + 'folder': os.path.basename(path.rstrip('/')), 'path_display': path, 'size': '' }) @@ -437,6 +426,13 @@ class Actions: self.app.restore_left_canvas_data.clear() self.app.restore_right_canvas_data.clear() + current_source = self.app.left_canvas_data.get('folder') + if current_source: + self.on_sidebar_button_click(current_source) + + self.app.backup_content_frame.system_backups_frame._load_backup_content() + self.app.backup_content_frame.user_backups_frame._load_backup_content() + with message_box_animation(self.app.animated_icon): MessageDialog(master=self.app, message_type="info", title=Msg.STR["settings_reset_title"], text=Msg.STR["settings_reset_text"]) @@ -469,7 +465,7 @@ class Actions: except ValueError: return 0 - def _set_ui_state(self, enable: bool, keep_cancel_enabled: bool = False): + def _set_ui_state(self, enable: bool, keep_cancel_enabled: bool = False, allow_log_and_backup_toggle: bool = False): # Sidebar Buttons for text, data in self.app.buttons_map.items(): # Find the actual button widget in the sidebar_buttons_frame @@ -492,6 +488,8 @@ class Actions: # Top Navigation Buttons for i, button in enumerate(self.app.nav_buttons): + if allow_log_and_backup_toggle and self.app.nav_buttons_defs[i][0] in [Msg.STR["log"], Msg.STR["backup_menu"]]: + continue button.config(state="normal" if enable else "disabled") # Right Canvas (Destination/Restore Source) @@ -622,21 +620,22 @@ class Actions: self.app.update_idletasks() self.app.log_window.clear_log() - self._set_ui_state(False) + self._set_ui_state(False, allow_log_and_backup_toggle=True) self.app.animated_icon.start() self.app._process_backup_queue() if self.app.mode == "backup": + source_size_bytes = self.app.source_size_bytes if self.app.vollbackup_var.get(): - self._start_system_backup("full") + self._start_system_backup("full", source_size_bytes) else: - self._start_system_backup("incremental") + self._start_system_backup("incremental", source_size_bytes) else: pass - def _start_system_backup(self, mode): + def _start_system_backup(self, mode, source_size_bytes): base_dest = self.app.destination_path if not base_dest: MessageDialog(master=self.app, message_type="error", diff --git a/pyimage_ui/advanced_settings_frame.py b/pyimage_ui/advanced_settings_frame.py index adcfe93..7820a05 100644 --- a/pyimage_ui/advanced_settings_frame.py +++ b/pyimage_ui/advanced_settings_frame.py @@ -21,10 +21,25 @@ class AdvancedSettingsFrame(tk.Toplevel): self, text=Msg.STR["advanced_settings_warning"], wraplength=780, justify="center") warning_label.pack(pady=10, fill=tk.X, padx=10) + # --- View Toggle Buttons --- + toggle_frame = ttk.Frame(self) + toggle_frame.pack(pady=5) + + self.system_excludes_button = ttk.Button( + toggle_frame, text=Msg.STR["system_excludes"], command=lambda: self._toggle_views("system")) + self.system_excludes_button.pack(side=tk.LEFT, padx=5) + + self.manual_excludes_button = ttk.Button( + toggle_frame, text=Msg.STR["manual_excludes"], command=lambda: self._toggle_views("manual")) + self.manual_excludes_button.pack(side=tk.LEFT, padx=5) + + # --- Container for the two views --- + view_container = ttk.Frame(self) + view_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + # --- Treeview for system folder exclusion --- self.tree_frame = ttk.LabelFrame( - self, text=Msg.STR["exclude_system_folders"], padding=10) - self.tree_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + view_container, text=Msg.STR["exclude_system_folders"], padding=10) columns = ("included", "name", "path") self.tree = ttk.Treeview( @@ -41,6 +56,16 @@ class AdvancedSettingsFrame(tk.Toplevel): self.tree.bind("", self._toggle_include_status) + # --- Manual Excludes Frame --- + self.manual_excludes_frame = ttk.LabelFrame( + view_container, text=Msg.STR["manual_excludes"], padding=10) + + self.manual_excludes_listbox = tk.Listbox(self.manual_excludes_frame, selectmode=tk.MULTIPLE) + self.manual_excludes_listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + + delete_button = ttk.Button(self.manual_excludes_frame, text=Msg.STR["delete"], command=self._delete_manual_exclude) + delete_button.pack(pady=5) + # --- Animation Settings --- animation_frame = ttk.LabelFrame( self, text=Msg.STR["animation_settings_title"], padding=10) @@ -106,6 +131,31 @@ class AdvancedSettingsFrame(tk.Toplevel): self._load_system_folders() self._load_animation_settings() self._load_backup_defaults() + self._load_manual_excludes() + + self._toggle_views("system") + + def _toggle_views(self, view): + if view == "system": + self.manual_excludes_frame.pack_forget() + self.tree_frame.pack(fill=tk.BOTH, expand=True) + else: + self.tree_frame.pack_forget() + self.manual_excludes_frame.pack(fill=tk.BOTH, expand=True) + + def _load_manual_excludes(self): + self.manual_excludes_listbox.delete(0, tk.END) + if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists(): + with open(AppConfig.MANUAL_EXCLUDE_LIST_PATH, 'r') as f: + for line in f: + line = line.strip() + if line: + self.manual_excludes_listbox.insert(tk.END, line) + + def _delete_manual_exclude(self): + selected_indices = self.manual_excludes_listbox.curselection() + for i in reversed(selected_indices): + self.manual_excludes_listbox.delete(i) def _reset_animation_settings(self): self.config_manager.remove_setting("backup_animation_type") @@ -285,6 +335,11 @@ class AdvancedSettingsFrame(tk.Toplevel): for path in final_excludes: f.write(f"{path}\n") + # Save manual excludes + with open(AppConfig.MANUAL_EXCLUDE_LIST_PATH, 'w') as f: + for item in self.manual_excludes_listbox.get(0, tk.END): + f.write(f"{item}\n") + self.destroy() if self.app_instance: