renema accurate to Incrementel

This commit is contained in:
2025-09-11 13:46:56 +02:00
parent d7dd4215c0
commit 3b57df2ffa
6 changed files with 91 additions and 80 deletions

View File

@@ -264,7 +264,7 @@ class BackupManager:
if not latest_backup_path:
self.logger.log(
"No previous full backup found. Accurate incremental size cannot be estimated, returning 0.")
"No previous full backup found. Incremental size cannot be estimated, returning 0.")
return 0
command = []
@@ -484,7 +484,7 @@ class BackupManager:
final_user_list = []
for source in sorted(user_backups_by_source.keys()):
source_backups = user_backups_by_source[source]
grouped_source_backups = []
temp_group = []
for backup in reversed(source_backups):
@@ -500,8 +500,9 @@ class BackupManager:
if temp_group:
grouped_source_backups.append(temp_group)
grouped_source_backups.sort(key=lambda g: g[0]['datetime'], reverse=True)
grouped_source_backups.sort(
key=lambda g: g[0]['datetime'], reverse=True)
for group in grouped_source_backups:
final_user_list.extend(group)

View File

@@ -265,12 +265,12 @@ class Msg:
"backup_finished_with_warnings": _("Backup finished with warnings. See log for details."),
"backup_failed": _("Backup failed. See log for details."),
"backup_cancelled_by_user": _("Backup was cancelled by the user."),
"accurate_size_cb_label": _("Accurate inkrem. size"),
"accurate_size_info_label": _("(Calculation may take longer)"),
"accurate_size_success": _("Accurate size calculated successfully."),
"accurate_size_failed": _("Failed to calculate size. See log for details."),
"incremental_size_cb_label": _("Inkrem. size"),
"incremental_size_info_label": _("(Calculation may take longer)"),
"incremental_size_success": _("Incremental size calculated successfully."),
"incremental_size_failed": _("Failed to calculate size. See log for details."),
"please_wait": _("Please wait, calculating..."),
"accurate_calc_cancelled": _("Calculate size cancelled."),
"incremental_calc_cancelled": _("Calculate size cancelled."),
"add_to_exclude_list": _("Add to exclude list"),
"exclude_dialog_text": _("Do you want to add a folder or a file?"),
"add_folder_button": _("Folder"),
@@ -278,6 +278,9 @@ class Msg:
"system_excludes": _("System Excludes"),
"manual_excludes": _("Manual Excludes"),
"manual_excludes_info": _("Here, manually add files or folders to be excluded from the backup. Each entry should be on a new line."),
"sudoers_info_text": _(f"To run automated backups, an administrator must create a file in /etc/sudoers.d/\n"
f"with the following content (replace 'user' with the correct username):\n"
f"user ALL=(ALL) NOPASSWD: /path/to/pybackup-cli.py"),
# Menus
"file_menu": _("File"),

View File

@@ -115,7 +115,7 @@ class MainApplication(tk.Tk):
self.calculation_thread = None
self.calculation_stop_event = None
self.source_larger_than_partition = False
self.accurate_calculation_running = False
self.incremental_calculation_running = False
self.is_first_backup = False
self.left_canvas_animation = None
@@ -473,16 +473,16 @@ class MainApplication(tk.Tk):
self.time_info_frame, text="Ende: --:--:--")
self.end_time_label.pack(side=tk.LEFT, padx=5)
accurate_size_frame = ttk.Frame(self.time_info_frame)
accurate_size_frame.pack(side=tk.LEFT, padx=20)
incremental_size_frame = ttk.Frame(self.time_info_frame)
incremental_size_frame.pack(side=tk.LEFT, padx=20)
self.accurate_size_btn = ttk.Button(accurate_size_frame, text=Msg.STR["accurate_size_cb_label"],
command=self.actions.on_accurate_size_calc)
self.accurate_size_btn.pack(side=tk.LEFT, padx=5)
self.incremental_size_btn = ttk.Button(incremental_size_frame, text=Msg.STR["incremental_size_cb_label"],
command=self.actions.on_incremental_size_calc)
self.incremental_size_btn.pack(side=tk.LEFT, padx=5)
accurate_size_info_label = ttk.Label(
accurate_size_frame, text=Msg.STR["accurate_size_info_label"], foreground="gray")
accurate_size_info_label.pack(side=tk.LEFT)
incremental_size_info_label = ttk.Label(
incremental_size_frame, text=Msg.STR["incremental_size_info_label"], foreground="gray")
incremental_size_info_label.pack(side=tk.LEFT)
checkbox_frame = ttk.Frame(self.info_checkbox_frame)
checkbox_frame.pack(fill=tk.X, pady=5)
@@ -530,9 +530,9 @@ class MainApplication(tk.Tk):
progress_container, orient="horizontal", length=100, mode="determinate")
self.task_progress.grid(row=1, column=0, sticky="ew", pady=(5, 0))
self.start_pause_button = ttk.Button(
self.start_cancel_button = ttk.Button(
self.action_frame, text=Msg.STR["start"], command=self.actions.toggle_start_cancel, state="disabled")
self.start_pause_button.grid(row=0, column=2, rowspan=2, padx=5)
self.start_cancel_button.grid(row=0, column=2, rowspan=2, padx=5)
def on_closing(self):
self.config_manager.set_setting(
@@ -597,9 +597,9 @@ class MainApplication(tk.Tk):
button_text, folder_size, mode_when_started = message
if mode_when_started != self.mode:
if calc_type == 'accurate_incremental':
if calc_type == 'incremental_incremental':
self.actions._set_ui_state(True)
self.accurate_calculation_running = False
self.incremental_calculation_running = False
self.animated_icon.stop("DISABLE")
else:
current_folder_name = self.left_canvas_data.get(
@@ -639,7 +639,7 @@ class MainApplication(tk.Tk):
self.drawing.update_target_projection()
if calc_type == 'accurate_incremental':
if calc_type == 'incremental_incremental':
self.source_size_bytes = folder_size
self.drawing.update_target_projection()
self.animated_icon.stop("DISABLE")
@@ -647,16 +647,16 @@ class MainApplication(tk.Tk):
self.task_progress.config(
mode="determinate", value=0)
self.actions._set_ui_state(True)
self.accurate_calculation_running = False
self.start_pause_button.config(
self.incremental_calculation_running = False
self.start_cancel_button.config(
text=Msg.STR["start"])
if status == 'success':
self.info_label.config(
text=Msg.STR["accurate_size_success"], foreground="#0078d7")
text=Msg.STR["incremental_size_success"], foreground="#0078d7")
self.current_file_label.config(text="")
else:
self.info_label.config(
text=Msg.STR["accurate_size_failed"], foreground="#D32F2F")
text=Msg.STR["incremental_size_failed"], foreground="#D32F2F")
self.current_file_label.config(text="")
elif isinstance(message, tuple) and len(message) == 2:
@@ -679,7 +679,7 @@ class MainApplication(tk.Tk):
else:
self.task_progress.stop()
elif message_type == 'cancel_button_state':
self.start_pause_button.config(state=value)
self.start_cancel_button.config(state=value)
elif message_type == 'current_path':
self.current_backup_path = value
app_logger.log(f"Set current backup path to: {value}")
@@ -692,7 +692,7 @@ class MainApplication(tk.Tk):
self.destination_path, initial_tab_index=active_tab_index)
elif message_type == 'error':
self.animated_icon.stop("DISABLE")
self.start_pause_button["text"] = "Start"
self.start_cancel_button["text"] = "Start"
self.backup_is_running = False
elif message_type == 'completion':
status_info = value
@@ -721,7 +721,7 @@ class MainApplication(tk.Tk):
pass
self.animated_icon.stop("DISABLE")
self.start_pause_button["text"] = "Start"
self.start_cancel_button["text"] = "Start"
self.task_progress["value"] = 0
self.current_file_label.config(text="")

View File

@@ -74,7 +74,7 @@ class Actions:
self.app.compressed_cb.config(state="normal")
self.app.encrypted_cb.config(state="normal")
self.app.test_run_cb.config(state="normal")
self.app.accurate_size_btn.config(state="normal")
self.app.incremental_size_btn.config(state="normal")
# Apply specific logic based on current states
if self.app.compressed_var.get():
@@ -85,8 +85,8 @@ class Actions:
# If compressed, cannot be encrypted
self.app.encrypted_var.set(False)
self.app.encrypted_cb.config(state="disabled")
# Accurate size not applicable for compressed
self.app.accurate_size_btn.config(state="disabled")
# Incremental size not applicable for compressed
self.app.incremental_size_btn.config(state="disabled")
elif self.app.encrypted_var.get():
# If encrypted, cannot be compressed
self.app.compressed_var.set(False)
@@ -113,16 +113,16 @@ class Actions:
def handle_encryption_change(self):
self._refresh_backup_options_ui()
def on_accurate_size_calc(self):
def on_incremental_size_calc(self):
if self.app.calculation_thread and self.app.calculation_thread.is_alive():
self.app.calculation_stop_event.set()
app_logger.log("Stopping previous size calculation.")
app_logger.log("Accurate incremental size calculation requested.")
self.app.accurate_calculation_running = True
app_logger.log("Incremental size calculation requested.")
self.app.incremental_calculation_running = True
self._set_ui_state(False, keep_cancel_enabled=True)
self.app.start_pause_button.config(text=Msg.STR["cancel_backup"])
self.app.start_cancel_button.config(text=Msg.STR["cancel_backup"])
self.app.drawing.reset_projection_canvases()
@@ -142,9 +142,9 @@ class Actions:
if not folder_path or not button_text:
app_logger.log(
"Cannot start accurate calculation, source folder info missing.")
"Cannot start incremental calculation, source folder info missing.")
self._set_ui_state(True)
self.app.accurate_calculation_running = False
self.app.incremental_calculation_running = False
self.app.animated_icon.stop("DISABLE")
return
@@ -180,9 +180,9 @@ class Actions:
app_logger.log(f"Error during threaded_incremental_calc: {e}")
status = 'failure'
finally:
if self.app.accurate_calculation_running:
if self.app.incremental_calculation_running:
self.app.queue.put(
(button_text, size, self.app.mode, 'accurate_incremental', status))
(button_text, size, self.app.mode, 'incremental_incremental', status))
self.app.calculation_thread = threading.Thread(
target=threaded_incremental_calc)
@@ -190,9 +190,9 @@ class Actions:
self.app.calculation_thread.start()
def on_sidebar_button_click(self, button_text):
if self.app.backup_is_running or self.app.accurate_calculation_running:
if self.app.backup_is_running or self.app.incremental_calculation_running:
app_logger.log(
"Action blocked: Backup or accurate calculation is in progress.")
"Action blocked: Backup or incremental calculation is in progress.")
return
self.app.drawing.reset_projection_canvases()
@@ -214,7 +214,7 @@ class Actions:
if not folder_path or not folder_path.exists():
print(
f"Folder not found for {canonical_key} (Path: {folder_path})")
self.app.start_pause_button.config(state="disabled")
self.app.start_cancel_button.config(state="disabled")
return
if self.app.mode == 'restore':
@@ -245,7 +245,7 @@ class Actions:
self.app._update_sync_mode_display()
def _start_left_canvas_calculation(self, button_text, folder_path, icon_name, extra_info):
self.app.start_pause_button.config(state="disabled")
self.app.start_cancel_button.config(state="disabled")
if self.app.mode == 'backup' and not self.app.destination_path:
self.app.left_canvas_data.update({
'icon': icon_name,
@@ -386,7 +386,7 @@ class Actions:
self.app.config_manager.set_setting(
"restore_source_path", path)
self.app.drawing.calculate_restore_folder_size()
self.app.start_pause_button.config(state="normal")
self.app.start_cancel_button.config(state="normal")
except FileNotFoundError:
with message_box_animation(self.app.animated_icon):
@@ -416,7 +416,7 @@ class Actions:
settings_frame.load_and_display_excludes()
settings_frame._load_hidden_files()
self.app.destination_path = None
self.app.start_pause_button.config(state="disabled")
self.app.start_cancel_button.config(state="disabled")
self.app.backup_left_canvas_data.clear()
self.app.backup_right_canvas_data.clear()
@@ -499,17 +499,17 @@ class Actions:
self.app.test_run_cb,
self.app.bypass_security_cb
]
self.app.accurate_size_btn.config(state="disabled")
self.app.incremental_size_btn.config(state="disabled")
for cb in checkboxes:
cb.config(state="disabled")
if keep_cancel_enabled:
self.app.start_pause_button.config(state="normal")
self.app.start_cancel_button.config(state="normal")
def toggle_start_cancel(self):
if self.app.accurate_calculation_running:
app_logger.log("Accurate size calculation cancelled by user.")
self.app.accurate_calculation_running = False
if self.app.incremental_calculation_running:
app_logger.log("Incremental size calculation cancelled by user.")
self.app.incremental_calculation_running = False
self.app.animated_icon.stop("DISABLE")
if self.app.left_canvas_animation:
@@ -520,8 +520,8 @@ class Actions:
self.app.task_progress.stop()
self.app.task_progress.config(mode="determinate", value=0)
self.app.info_label.config(
text=Msg.STR["accurate_calc_cancelled"], foreground="#E8740C")
self.app.start_pause_button.config(text=Msg.STR["start"])
text=Msg.STR["incremental_calc_cancelled"], foreground="#E8740C")
self.app.start_cancel_button.config(text=Msg.STR["start"])
self._set_ui_state(True)
return
@@ -571,11 +571,11 @@ class Actions:
self.app.current_backup_path = None
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self.app.start_cancel_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
else:
if self.app.start_pause_button['state'] == 'disabled':
if self.app.start_cancel_button['state'] == 'disabled':
return
self.app.backup_is_running = True
@@ -588,7 +588,7 @@ class Actions:
self.app.info_label.config(text="Backup wird vorbereitet...")
self.app._update_duration()
self.app.start_pause_button["text"] = Msg.STR["cancel_backup"]
self.app.start_cancel_button["text"] = Msg.STR["cancel_backup"]
self.app.update_idletasks()
self._set_ui_state(False, allow_log_and_backup_toggle=True)
@@ -603,7 +603,7 @@ class Actions:
app_logger.log(
"No source folder selected, aborting backup.")
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self.app.start_cancel_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
return
@@ -668,7 +668,7 @@ class Actions:
MessageDialog(message_type="error",
title=Msg.STR["error"], text=Msg.STR["err_no_dest_folder"])
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self.app.start_cancel_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
return

View File

@@ -172,9 +172,7 @@ class AdvancedSettingsFrame(ttk.Frame):
self.keyfile_settings_frame, textvariable=self.key_file_status_var, foreground="gray")
key_file_status_label.grid(row=0, column=1, padx=5, pady=5, sticky="w")
sudoers_info_text = (f"To run automated backups, an administrator must create a file in /etc/sudoers.d/\n"
f"with the following content (replace 'punix' with the correct username):\n"
f"punix ALL=(ALL) NOPASSWD: /path/to/pybackup-cli.py")
sudoers_info_text = Msg.STR["sudoers_info_text"]
sudoers_info_label = ttk.Label(
self.keyfile_settings_frame, text=sudoers_info_text, justify="left")

View File

@@ -166,7 +166,7 @@ class Drawing:
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")
self.app.start_cancel_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):
self.app.right_canvas_data['calculating'] = True
@@ -213,7 +213,7 @@ class Drawing:
self.redraw_right_canvas_restore()
if not self.app.left_canvas_data.get('calculating', False):
self.app.start_pause_button.config(state="normal")
self.app.start_cancel_button.config(state="normal")
if not stop_event.is_set():
self.app.after(0, update_ui)
@@ -240,9 +240,10 @@ class Drawing:
required_space *= 2 # Double the space for compression process
projected_total_used = self.app.destination_used_bytes + required_space
if self.app.destination_total_bytes > 0:
projected_total_percentage = projected_total_used / self.app.destination_total_bytes
projected_total_percentage = projected_total_used / \
self.app.destination_total_bytes
else:
projected_total_percentage = 0
@@ -251,35 +252,42 @@ class Drawing:
# First, check for critical space issues
if projected_total_used > self.app.destination_total_bytes or projected_total_percentage >= 0.95:
self.app.start_pause_button.config(state="disabled")
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(), fill="#D32F2F", outline="") # Red bar
self.app.start_cancel_button.config(state="disabled")
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(
), fill="#D32F2F", outline="") # Red bar
info_messages.append(Msg.STR["warning_not_enough_space"])
elif projected_total_percentage >= 0.90:
self.app.start_pause_button.config(state="normal")
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(), fill="#E8740C", outline="") # Orange bar
self.app.start_cancel_button.config(state="normal")
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(
), fill="#E8740C", outline="") # Orange bar
info_messages.append(Msg.STR["warning_space_over_90_percent"])
else:
# Only enable the button if the source is not larger than the partition itself
if not self.app.source_larger_than_partition:
self.app.start_pause_button.config(state="normal")
self.app.start_cancel_button.config(state="normal")
else:
self.app.start_pause_button.config(state="disabled")
self.app.start_cancel_button.config(state="disabled")
used_percentage = self.app.destination_used_bytes / self.app.destination_total_bytes
used_percentage = self.app.destination_used_bytes / \
self.app.destination_total_bytes
used_width = canvas_width * used_percentage
canvas.create_rectangle(0, 0, used_width, canvas.winfo_height(), fill="#0078d7", outline="")
canvas.create_rectangle(
0, 0, used_width, canvas.winfo_height(), fill="#0078d7", outline="")
# Draw the projected part only if there is space
projected_percentage = self.app.source_size_bytes / self.app.destination_total_bytes
projected_percentage = self.app.source_size_bytes / \
self.app.destination_total_bytes
projected_width = canvas_width * projected_percentage
canvas.create_rectangle(used_width, 0, used_width + projected_width, canvas.winfo_height(), fill="#50E6FF", outline="")
canvas.create_rectangle(used_width, 0, used_width + projected_width,
canvas.winfo_height(), fill="#50E6FF", outline="")
# Add other informational messages if no critical warnings are present
if not info_messages:
if self.app.source_larger_than_partition:
info_messages.append(Msg.STR["warning_source_larger_than_partition"])
info_messages.append(
Msg.STR["warning_source_larger_than_partition"])
elif self.app.is_first_backup:
info_messages.append(Msg.STR["ready_for_first_backup"])
elif self.app.mode == "backup":
@@ -287,7 +295,8 @@ class Drawing:
else:
info_messages.append(Msg.STR["restore_mode_info"])
self.app.info_label.config(text="\n".join(info_messages), font=info_font)
self.app.info_label.config(
text="\n".join(info_messages), font=info_font)
self.app.target_size_label.config(
text=f"{projected_total_used / (1024**3):.2f} GB / {self.app.destination_total_bytes / (1024**3):.2f} GB")