This commit addresses several bugs and improves the logic of the settings panels. - fix(settings): The "Reset to default settings" action no longer deletes the user-defined file/folder exclusion list. - fix(settings): Corrected a bug in the "Add to exclude list" function where new entries were not being written correctly. The logic is now more robust and prevents duplicate entries. - fix(ui): Fixed a TclError crash in Advanced Settings caused by mixing `grid` and `pack` geometry managers. - feat(settings): Implemented mutual exclusivity for the "trash bin" and "compression/encryption" checkboxes to prevent invalid configurations. - i18n: Improved and clarified the English text for the trash bin feature descriptions to enable better translation.
716 lines
30 KiB
Python
716 lines
30 KiB
Python
# pyimage_ui/actions.py
|
|
import tkinter as tk
|
|
import os
|
|
import shutil
|
|
import threading
|
|
import datetime
|
|
import locale
|
|
from typing import Optional
|
|
|
|
from shared_libs.message import MessageDialog
|
|
from shared_libs.custom_file_dialog import CustomFileDialog
|
|
from core.pbp_app_config import AppConfig, Msg
|
|
from shared_libs.logger import app_logger
|
|
from shared_libs.common_tools import message_box_animation
|
|
|
|
|
|
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)
|
|
elif backup_type == "incremental":
|
|
self.app.vollbackup_var.set(False)
|
|
self.app.inkrementell_var.set(True)
|
|
|
|
def _update_backup_type_controls(self):
|
|
source_name = self.app.left_canvas_data.get('folder')
|
|
is_system_backup = (source_name == "Computer")
|
|
|
|
# Re-enable controls for user backups, disable for system unless conditions are met
|
|
if not is_system_backup:
|
|
self.app.full_backup_cb.config(state='normal')
|
|
self.app.incremental_cb.config(state='normal')
|
|
else: # System backup
|
|
self.app.full_backup_cb.config(state='normal')
|
|
self.app.incremental_cb.config(state='normal')
|
|
|
|
# Handle forced settings from advanced config, which have top priority
|
|
if self.app.config_manager.get_setting("force_full_backup", False):
|
|
self._set_backup_type("full")
|
|
self.app.full_backup_cb.config(state='disabled')
|
|
self.app.incremental_cb.config(state='disabled')
|
|
return
|
|
if self.app.config_manager.get_setting("force_incremental_backup", False):
|
|
self._set_backup_type("incremental")
|
|
self.app.full_backup_cb.config(state='disabled')
|
|
self.app.incremental_cb.config(state='disabled')
|
|
return
|
|
|
|
# Default to Full if no destination is set
|
|
if not self.app.destination_path or not os.path.isdir(self.app.destination_path):
|
|
self._set_backup_type("full")
|
|
return
|
|
|
|
is_encrypted = self.app.encrypted_var.get()
|
|
|
|
# Use the new detection logic for both user and system backups
|
|
# Note: For system backups, source_name is "Computer". We might need a more specific profile name.
|
|
# For now, we adapt it to the check_for_full_backup function's expectation.
|
|
profile_name = "system" if is_system_backup else source_name
|
|
|
|
full_backup_exists = self.app.backup_manager.check_for_full_backup(
|
|
dest_path=self.app.destination_path,
|
|
source_name=profile_name, # Using a profile name now
|
|
is_encrypted=is_encrypted
|
|
)
|
|
|
|
if full_backup_exists:
|
|
self._set_backup_type("incremental")
|
|
else:
|
|
self._set_backup_type("full")
|
|
|
|
def _refresh_backup_options_ui(self):
|
|
# Reset enabled/disabled state for all potentially affected controls
|
|
self.app.full_backup_cb.config(state="normal")
|
|
self.app.incremental_cb.config(state="normal")
|
|
self.app.compressed_cb.config(state="normal")
|
|
self.app.encrypted_cb.config(state="normal")
|
|
self.app.accurate_size_cb.config(state="normal")
|
|
|
|
# Apply logic: Encryption and Compression are mutually exclusive
|
|
if self.app.encrypted_var.get():
|
|
self.app.compressed_var.set(False)
|
|
self.app.compressed_cb.config(state="disabled")
|
|
|
|
if self.app.compressed_var.get():
|
|
self.app.encrypted_var.set(False)
|
|
self.app.encrypted_cb.config(state="disabled")
|
|
# Compression forces 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")
|
|
self.app.genaue_berechnung_var.set(False)
|
|
|
|
# After setting the states, determine the final full/incremental choice
|
|
self._update_backup_type_controls()
|
|
|
|
def handle_backup_type_change(self, changed_var_name):
|
|
if changed_var_name == 'voll':
|
|
if self.app.vollbackup_var.get():
|
|
self._set_backup_type("full")
|
|
elif changed_var_name == 'inkrementell':
|
|
if self.app.inkrementell_var.get():
|
|
self._set_backup_type("incremental")
|
|
|
|
def handle_compression_change(self):
|
|
self._refresh_backup_options_ui()
|
|
|
|
def handle_encryption_change(self):
|
|
self._refresh_backup_options_ui()
|
|
|
|
def on_toggle_accurate_size_calc(self):
|
|
if not self.app.genaue_berechnung_var.get():
|
|
return
|
|
|
|
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
|
|
self._set_ui_state(False, keep_cancel_enabled=True)
|
|
self.app.start_pause_button.config(text=Msg.STR["cancel_backup"])
|
|
|
|
self.app.drawing.reset_projection_canvases()
|
|
|
|
self.app.info_label.config(
|
|
text=Msg.STR["please_wait"], foreground="#0078d7")
|
|
self.app.task_progress.config(mode="indeterminate")
|
|
self.app.task_progress.start()
|
|
self.app.left_canvas_data.update({
|
|
'size': Msg.STR["calculating_size"],
|
|
'calculating': True,
|
|
})
|
|
self.app.drawing.start_backup_calculation_display()
|
|
self.app.animated_icon.start()
|
|
|
|
folder_path = self.app.left_canvas_data.get('path_display')
|
|
button_text = self.app.left_canvas_data.get('folder')
|
|
|
|
if not folder_path or not button_text:
|
|
app_logger.log(
|
|
"Cannot start accurate calculation, source folder info missing.")
|
|
self._set_ui_state(True)
|
|
self.app.genaue_berechnung_var.set(False)
|
|
self.app.accurate_calculation_running = False
|
|
self.app.animated_icon.stop("DISABLE")
|
|
return
|
|
|
|
def threaded_incremental_calc():
|
|
status = 'failure'
|
|
size = 0
|
|
try:
|
|
exclude_file_paths = []
|
|
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(
|
|
AppConfig.GENERATED_EXCLUDE_LIST_PATH)
|
|
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(AppConfig.USER_EXCLUDE_LIST_PATH)
|
|
if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(
|
|
AppConfig.MANUAL_EXCLUDE_LIST_PATH)
|
|
|
|
base_dest = self.app.destination_path
|
|
correct_parent_dir = os.path.join(base_dest, "pybackup")
|
|
dummy_dest_for_calc = os.path.join(
|
|
correct_parent_dir, "dummy_name")
|
|
|
|
size = self.app.data_processing.get_incremental_backup_size(
|
|
source_path=folder_path,
|
|
dest_path=dummy_dest_for_calc,
|
|
is_system=True,
|
|
exclude_files=exclude_file_paths
|
|
)
|
|
status = 'success' if size > 0 else 'failure'
|
|
except Exception as e:
|
|
app_logger.log(f"Error during threaded_incremental_calc: {e}")
|
|
status = 'failure'
|
|
finally:
|
|
if self.app.accurate_calculation_running:
|
|
self.app.queue.put(
|
|
(button_text, size, self.app.mode, 'accurate_incremental', status))
|
|
|
|
self.app.calculation_thread = threading.Thread(
|
|
target=threaded_incremental_calc)
|
|
self.app.calculation_thread.daemon = True
|
|
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:
|
|
app_logger.log(
|
|
"Action blocked: Backup or accurate calculation is in progress.")
|
|
return
|
|
|
|
self.app.drawing.reset_projection_canvases()
|
|
if not self.app.canvas_frame.winfo_viewable():
|
|
self.app.navigation.toggle_mode(
|
|
self.app.mode, trigger_calculation=False)
|
|
|
|
# self.app.log_window.clear_log()
|
|
|
|
REVERSE_FOLDER_MAP = {
|
|
"Computer": "Computer",
|
|
Msg.STR["cat_documents"]: "Documents",
|
|
Msg.STR["cat_images"]: "Pictures",
|
|
Msg.STR["cat_music"]: "Music",
|
|
Msg.STR["cat_videos"]: "Videos",
|
|
}
|
|
canonical_key = REVERSE_FOLDER_MAP.get(button_text, button_text)
|
|
|
|
folder_path = AppConfig.FOLDER_PATHS.get(canonical_key)
|
|
|
|
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")
|
|
return
|
|
|
|
if self.app.mode == 'restore':
|
|
try:
|
|
total, used, free = shutil.disk_usage(str(folder_path))
|
|
self.app.destination_total_bytes = total
|
|
self.app.destination_used_bytes = used
|
|
except FileNotFoundError:
|
|
app_logger.log(f"Path not found for disk usage: {folder_path}")
|
|
return
|
|
|
|
icon_name = self.app.buttons_map[button_text]['icon']
|
|
|
|
extra_info = ""
|
|
if button_text == "Computer":
|
|
if self.app.mode == 'backup':
|
|
extra_info = Msg.STR["system_backup_info"]
|
|
else:
|
|
extra_info = Msg.STR["system_restore_info"]
|
|
else:
|
|
if self.app.mode == 'backup':
|
|
extra_info = Msg.STR["user_backup_info"]
|
|
else:
|
|
extra_info = Msg.STR["user_restore_info"]
|
|
|
|
if self.app.mode == 'backup':
|
|
self._update_backup_type_controls()
|
|
else:
|
|
self.app.config_manager.set_setting(
|
|
"restore_destination_path", folder_path)
|
|
|
|
self._start_left_canvas_calculation(
|
|
button_text, str(folder_path), icon_name, extra_info)
|
|
# Update sync mode display when source changes
|
|
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")
|
|
if self.app.mode == 'backup' and not self.app.destination_path:
|
|
self.app.left_canvas_data.update({
|
|
'icon': icon_name,
|
|
'folder': button_text,
|
|
'path_display': folder_path,
|
|
'size': Msg.STR["select_destination_first"],
|
|
'calculating': False,
|
|
'extra_info': extra_info
|
|
})
|
|
self.app.drawing.redraw_left_canvas()
|
|
return
|
|
|
|
if self.app.mode == 'restore' and not self.app.right_canvas_data.get('path_display'):
|
|
self.app.left_canvas_data.update({
|
|
'icon': icon_name,
|
|
'folder': button_text,
|
|
'path_display': folder_path,
|
|
'size': Msg.STR["select_source_first"],
|
|
'calculating': False,
|
|
'extra_info': extra_info
|
|
})
|
|
self.app.drawing.redraw_left_canvas()
|
|
return
|
|
|
|
if self.app.calculation_thread and self.app.calculation_thread.is_alive():
|
|
self.app.calculation_stop_event.set()
|
|
|
|
data_dict = self.app.left_canvas_data
|
|
data_dict.update({
|
|
'icon': icon_name,
|
|
'folder': button_text,
|
|
'path_display': folder_path,
|
|
'size': Msg.STR["calculating_size"],
|
|
'calculating': True,
|
|
'extra_info': extra_info
|
|
})
|
|
|
|
self.app.drawing.start_backup_calculation_display()
|
|
|
|
self.app.calculation_stop_event = threading.Event()
|
|
|
|
if button_text == "Computer":
|
|
app_logger.log(
|
|
"Using default (full) size calculation for system backup display.")
|
|
exclude_patterns = self.app.data_processing.load_exclude_patterns()
|
|
target_method = self.app.data_processing.get_folder_size_threaded
|
|
args = (folder_path, button_text, self.app.calculation_stop_event,
|
|
exclude_patterns, self.app.mode)
|
|
else:
|
|
target_method = self.app.data_processing.get_user_folder_size_threaded
|
|
args = (folder_path, button_text,
|
|
self.app.calculation_stop_event, self.app.mode)
|
|
|
|
self.app.calculation_thread = threading.Thread(
|
|
target=target_method, args=args)
|
|
self.app.calculation_thread.daemon = True
|
|
self.app.calculation_thread.start()
|
|
|
|
if self.app.mode == 'backup':
|
|
self._update_backup_type_controls()
|
|
else:
|
|
self.app.config_manager.set_setting(
|
|
"restore_destination_path", folder_path)
|
|
|
|
def on_right_canvas_click(self, event):
|
|
if self.app.backup_is_running:
|
|
app_logger.log("Action blocked: Backup is in progress.")
|
|
return
|
|
|
|
self.app.drawing.reset_projection_canvases()
|
|
self.app.after(100, self._open_source_or_destination_dialog)
|
|
|
|
def _open_source_or_destination_dialog(self):
|
|
title = Msg.STR["select_dest_folder_title"] if self.app.mode == 'backup' else Msg.STR["select_restore_source_title"]
|
|
path = self._get_path_from_dialog(title)
|
|
if path:
|
|
self._update_right_canvas_info(path)
|
|
|
|
def _get_path_from_dialog(self, title) -> Optional[str]:
|
|
self.app.update_idletasks()
|
|
dialog = CustomFileDialog(self.app, mode="dir", title=title)
|
|
self.app.wait_window(dialog)
|
|
path = dialog.get_result()
|
|
dialog.destroy()
|
|
return path
|
|
|
|
def _update_right_canvas_info(self, path):
|
|
try:
|
|
if self.app.mode == "backup":
|
|
# Unmount previous destination if it was mounted
|
|
if self.app.destination_path:
|
|
self.app.backup_manager.encryption_manager.unmount(
|
|
self.app.destination_path)
|
|
|
|
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:
|
|
lines = f.readlines()
|
|
f.seek(0)
|
|
if not any(backup_root_to_exclude in line for line in lines):
|
|
lines.append(f"\n{backup_root_to_exclude}\n")
|
|
f.writelines(lines)
|
|
except FileNotFoundError:
|
|
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'w') as f:
|
|
f.write(f"{backup_root_to_exclude}\n")
|
|
except IOError as e:
|
|
app_logger.log(f"Error updating exclusion list: {e}")
|
|
|
|
total, used, free = shutil.disk_usage(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('/')),
|
|
'path_display': path,
|
|
'size': size_str
|
|
})
|
|
self.app.config_manager.set_setting(
|
|
"backup_destination_path", path)
|
|
self.app.header_frame.refresh_status() # Refresh keyring status
|
|
self.app.drawing.redraw_right_canvas()
|
|
self.app.drawing.update_target_projection()
|
|
|
|
current_source = self.app.left_canvas_data.get('folder')
|
|
if current_source:
|
|
self.on_sidebar_button_click(current_source)
|
|
self._update_backup_type_controls()
|
|
|
|
elif self.app.mode == "restore":
|
|
self.app.right_canvas_data.update({
|
|
'folder': os.path.basename(path.rstrip('/')),
|
|
'path_display': path,
|
|
'size': ''
|
|
})
|
|
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")
|
|
|
|
except FileNotFoundError:
|
|
with message_box_animation(self.app.calculating_animation):
|
|
MessageDialog(master=self.app, message_type="error",
|
|
title=Msg.STR["error"], text=Msg.STR["path_not_found"].format(path=path)).show()
|
|
|
|
def reset_to_default_settings(self):
|
|
|
|
|
|
self.app.config_manager.set_setting("backup_destination_path", None)
|
|
self.app.config_manager.set_setting("restore_source_path", None)
|
|
self.app.config_manager.set_setting("restore_destination_path", None)
|
|
|
|
self.app.config_manager.remove_setting("backup_animation_type")
|
|
self.app.config_manager.remove_setting("calculation_animation_type")
|
|
self.app.config_manager.remove_setting("force_full_backup")
|
|
self.app.config_manager.remove_setting("force_incremental_backup")
|
|
self.app.config_manager.remove_setting("force_compression")
|
|
self.app.config_manager.remove_setting("force_encryption")
|
|
|
|
self.app.update_backup_options_from_config()
|
|
|
|
AppConfig.generate_and_write_final_exclude_list()
|
|
app_logger.log("Settings have been reset to default values.")
|
|
|
|
settings_frame = self.app.settings_frame
|
|
if settings_frame:
|
|
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.backup_left_canvas_data.clear()
|
|
self.app.backup_right_canvas_data.clear()
|
|
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"])
|
|
|
|
def _parse_size_string_to_bytes(self, size_str: str) -> int:
|
|
if not size_str or size_str == Msg.STR["calculating_size"]:
|
|
return 0
|
|
|
|
parts = size_str.split()
|
|
if len(parts) != 2:
|
|
return 0
|
|
|
|
try:
|
|
value = float(parts[0])
|
|
unit = parts[1].upper()
|
|
|
|
if unit == 'B':
|
|
return int(value)
|
|
elif unit == 'KB':
|
|
return int(value * (1024**1))
|
|
elif unit == 'MB':
|
|
return int(value * (1024**2))
|
|
elif unit == 'GB':
|
|
return int(value * (1024**3))
|
|
elif unit == 'TB':
|
|
return int(value * (1024**4))
|
|
else:
|
|
return 0
|
|
except ValueError:
|
|
return 0
|
|
|
|
def _set_ui_state(self, enable: bool, keep_cancel_enabled: bool = False, allow_log_and_backup_toggle: bool = False):
|
|
for text, data in self.app.buttons_map.items():
|
|
for child in self.app.sidebar_buttons_frame.winfo_children():
|
|
if isinstance(child, tk.ttk.Button) and child.cget("text") == text:
|
|
child.config(state="normal" if enable else "disabled")
|
|
break
|
|
|
|
self.app.schedule_dialog_button.config(
|
|
state="normal" if enable else "disabled")
|
|
self.app.settings_button.config(
|
|
state="normal" if enable else "disabled")
|
|
|
|
self.app.mode_button.config(state="normal" if enable else "disabled")
|
|
|
|
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")
|
|
|
|
if enable:
|
|
self.app.right_canvas.bind(
|
|
"<Button-1>", self.app.actions.on_right_canvas_click)
|
|
self.app.right_canvas.config(cursor="hand2")
|
|
else:
|
|
self.app.right_canvas.unbind("<Button-1>")
|
|
self.app.right_canvas.config(cursor="")
|
|
|
|
if enable:
|
|
self.app.update_backup_options_from_config()
|
|
self.app.actions._update_backup_type_controls()
|
|
else:
|
|
checkboxes = [
|
|
self.app.full_backup_cb,
|
|
self.app.incremental_cb,
|
|
self.app.accurate_size_cb,
|
|
self.app.compressed_cb,
|
|
self.app.encrypted_cb,
|
|
self.app.test_run_cb,
|
|
self.app.bypass_security_cb
|
|
]
|
|
for cb in checkboxes:
|
|
cb.config(state="disabled")
|
|
|
|
if keep_cancel_enabled:
|
|
self.app.start_pause_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
|
|
self.app.genaue_berechnung_var.set(False)
|
|
|
|
self.app.animated_icon.stop("DISABLE")
|
|
if self.app.left_canvas_animation:
|
|
self.app.left_canvas_animation.stop()
|
|
self.app.left_canvas_data['calculating'] = False
|
|
self.app.drawing.redraw_left_canvas()
|
|
|
|
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"])
|
|
self._set_ui_state(True)
|
|
return
|
|
|
|
if self.app.backup_is_running:
|
|
self.app.animated_icon.stop("DISABLE")
|
|
|
|
delete_path = getattr(self.app, 'current_backup_path', None)
|
|
is_system_backup = self.app.backup_manager.is_system_process
|
|
|
|
if is_system_backup:
|
|
if delete_path:
|
|
self.app.backup_manager.cancel_and_delete_privileged_backup(
|
|
delete_path)
|
|
app_logger.log(
|
|
Msg.STR["backup_cancelled_and_deleted_msg"])
|
|
self.app.info_label.config(
|
|
text=Msg.STR["backup_cancelled_and_deleted_msg"])
|
|
else:
|
|
self.app.backup_manager.cancel_backup()
|
|
app_logger.log(
|
|
"Backup cancelled, but directory could not be deleted (path unknown).")
|
|
self.app.info_label.config(
|
|
text="Backup cancelled, but directory could not be deleted (path unknown).")
|
|
else:
|
|
self.app.backup_manager.cancel_backup()
|
|
if delete_path:
|
|
try:
|
|
app_logger.log(
|
|
f"Attempting to delete non-privileged path: {delete_path}")
|
|
if os.path.isdir(delete_path):
|
|
shutil.rmtree(delete_path)
|
|
app_logger.log(
|
|
Msg.STR["backup_cancelled_and_deleted_msg"])
|
|
self.app.info_label.config(
|
|
text=Msg.STR["backup_cancelled_and_deleted_msg"])
|
|
except Exception as e:
|
|
app_logger.log(f"Error deleting backup directory: {e}")
|
|
self.app.info_label.config(
|
|
text=f"Error deleting backup directory: {e}")
|
|
else:
|
|
app_logger.log(
|
|
"Backup cancelled, but no path found to delete.")
|
|
self.app.info_label.config(
|
|
text="Backup cancelled, but no path found to delete.")
|
|
|
|
if hasattr(self.app, 'current_backup_path'):
|
|
self.app.current_backup_path = None
|
|
|
|
self.app.backup_is_running = False
|
|
self.app.start_pause_button["text"] = Msg.STR["start"]
|
|
self._set_ui_state(True)
|
|
|
|
else:
|
|
if self.app.start_pause_button['state'] == 'disabled':
|
|
return
|
|
|
|
self.app.backup_is_running = True
|
|
|
|
self.app.start_time = datetime.datetime.now()
|
|
start_str = self.app.start_time.strftime("%H:%M:%S")
|
|
self.app.start_time_label.config(text=f"Start: {start_str}")
|
|
self.app.end_time_label.config(text="Ende: --:--:--")
|
|
self.app.duration_label.config(text="Dauer: --:--:--")
|
|
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.update_idletasks()
|
|
|
|
# self.app.log_window.clear_log()
|
|
self._set_ui_state(False, allow_log_and_backup_toggle=True)
|
|
|
|
self.app.animated_icon.start()
|
|
|
|
if self.app.mode == "backup":
|
|
source_folder = self.app.left_canvas_data.get('folder')
|
|
source_size_bytes = self.app.source_size_bytes
|
|
|
|
if not source_folder:
|
|
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._set_ui_state(True)
|
|
return
|
|
|
|
if source_folder == "Computer":
|
|
mode = "full" if self.app.vollbackup_var.get() else "incremental"
|
|
self._start_system_backup(mode, source_size_bytes)
|
|
else:
|
|
self._start_user_backup()
|
|
else: # restore mode
|
|
# Restore logic would go here
|
|
pass
|
|
|
|
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",
|
|
title=Msg.STR["error"], text=Msg.STR["err_no_dest_folder"])
|
|
return
|
|
|
|
if base_dest.startswith("/home"):
|
|
with message_box_animation(self.app.animated_icon):
|
|
MessageDialog(master=self.app, message_type="error",
|
|
title=Msg.STR["error"], text=Msg.STR["system_backup_in_home_error"])
|
|
return
|
|
|
|
is_encrypted = self.app.encrypted_var.get()
|
|
# Password handling is now managed within the backup manager if needed for mounting
|
|
|
|
source_size_bytes = self.app.left_canvas_data.get('total_bytes', 0)
|
|
|
|
exclude_file_paths = []
|
|
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(AppConfig.GENERATED_EXCLUDE_LIST_PATH)
|
|
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(AppConfig.USER_EXCLUDE_LIST_PATH)
|
|
if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists():
|
|
exclude_file_paths.append(AppConfig.MANUAL_EXCLUDE_LIST_PATH)
|
|
|
|
is_dry_run = self.app.testlauf_var.get()
|
|
is_compressed = self.app.compressed_var.get()
|
|
|
|
self.app.backup_manager.start_backup(
|
|
queue=self.app.queue,
|
|
source_path="/",
|
|
dest_path=base_dest, # Pass the base destination path
|
|
is_system=True,
|
|
source_name="system",
|
|
is_dry_run=is_dry_run,
|
|
exclude_files=exclude_file_paths,
|
|
source_size=source_size_bytes,
|
|
is_compressed=is_compressed,
|
|
is_encrypted=is_encrypted,
|
|
mode=mode)
|
|
|
|
def _start_user_backup(self):
|
|
base_dest = self.app.destination_path
|
|
source_path = self.app.left_canvas_data.get('path_display')
|
|
source_name = self.app.left_canvas_data.get('folder')
|
|
source_size_bytes = self.app.left_canvas_data.get('total_bytes', 0)
|
|
|
|
if not base_dest or not source_path:
|
|
MessageDialog(master=self.app, 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._set_ui_state(True)
|
|
return
|
|
|
|
is_encrypted = self.app.encrypted_var.get()
|
|
# Password handling is now managed within the backup manager if needed for mounting
|
|
|
|
mode = "full" if self.app.vollbackup_var.get() else "incremental"
|
|
|
|
is_dry_run = self.app.testlauf_var.get()
|
|
is_compressed = self.app.compressed_var.get()
|
|
use_trash_bin = self.app.config_manager.get_setting(
|
|
"use_trash_bin", False)
|
|
no_trash_bin = self.app.config_manager.get_setting(
|
|
"no_trash_bin", False)
|
|
|
|
self.app.backup_manager.start_backup(
|
|
queue=self.app.queue,
|
|
source_path=source_path,
|
|
dest_path=base_dest, # Pass the base destination path
|
|
is_system=False,
|
|
source_name=source_name,
|
|
is_dry_run=is_dry_run,
|
|
exclude_files=None, # User backups don't use the global exclude list here
|
|
source_size=source_size_bytes,
|
|
is_compressed=is_compressed,
|
|
is_encrypted=is_encrypted,
|
|
mode=mode,
|
|
use_trash_bin=use_trash_bin,
|
|
no_trash_bin=no_trash_bin)
|