Dieses Commit ersetzt die meisten ttk.Checkbutton- und ttk.Radiobutton-Widgets in der gesamten Anwendung durch einen benutzerdefinierten "Switch"-Stil, um ein moderneres Erscheinungsbild zu erzielen. Die Änderungen umfassen: - **Hauptfenster**: - Umwandlung der Backup-Optionen (Voll, Inkrementell, Komprimiert, Verschlüsselt) in Switches. - Ersetzung der "Genaue Grössenberechnung"-Checkbox durch einen normalen Button. - Verschiebung der "Testlauf"- und "Sicherheit umgehen"-Switches in die Seitenleiste unter "Einstellungen". - **Scheduler**: - Ersetzung aller Radio-Buttons und Checkboxen durch Switches, mit implementierter Logik zur Gewährleistung der exklusiven Auswahl für Backup-Typ und Frequenz. - **Erweiterte Einstellungen**: - Umwandlung aller Checkboxen im Abschnitt "Backup-Standards" in Switches. - **Styling**: - Hinzufügen eines neuen Stils `Switch2.TCheckbutton` für die Switches in der Seitenleiste, um sie an das dunkle Thema der Seitenleiste anzupassen. Die Konfiguration erfolgt direkt in `main_app.py`. - **Fehlerbehebungen**: - Behebung eines `AttributeError`-Absturzes, der durch die Verschiebung der Switches vor der Deklaration ihrer `tk.BooleanVar`-Variablen verursacht wurde. - Anpassung der zugehörigen Logik in `pyimage_ui/actions.py` an den neuen Button.
550 lines
25 KiB
Python
550 lines
25 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from core.pbp_app_config import AppConfig, Msg
|
|
from shared_libs.animated_icon import AnimatedIcon
|
|
from pyimage_ui.shared_logic import enforce_backup_type_exclusivity
|
|
from shared_libs.message import MessageDialog
|
|
from pyimage_ui.password_dialog import PasswordDialog
|
|
|
|
|
|
class AdvancedSettingsFrame(ttk.Frame):
|
|
def __init__(self, master, config_manager, app_instance, show_main_settings_callback, **kwargs):
|
|
super().__init__(master, **kwargs)
|
|
|
|
self.show_main_settings_callback = show_main_settings_callback
|
|
self.config_manager = config_manager
|
|
self.app_instance = app_instance
|
|
self.current_view_index = 0
|
|
|
|
nav_frame = ttk.Frame(self)
|
|
nav_frame.pack(fill=tk.X, padx=10, pady=(0, 5))
|
|
ttk.Separator(nav_frame, orient=tk.HORIZONTAL).pack(
|
|
fill=tk.X, pady=(0, 15))
|
|
top_nav_frame = ttk.Frame(nav_frame)
|
|
top_nav_frame.pack(side=tk.LEFT)
|
|
|
|
self.nav_buttons_defs = [
|
|
(Msg.STR["system_excludes"], lambda: self._switch_view(0)),
|
|
(Msg.STR["manual_excludes"], lambda: self._switch_view(1)),
|
|
(Msg.STR["keyfile_settings"], lambda: self._switch_view(2)),
|
|
(Msg.STR["animation_settings_title"],
|
|
lambda: self._switch_view(3)),
|
|
(Msg.STR["backup_defaults_title"],
|
|
lambda: self._switch_view(4)),
|
|
]
|
|
|
|
self.nav_buttons = []
|
|
self.nav_progress_bars = []
|
|
|
|
for i, (text, command) in enumerate(self.nav_buttons_defs):
|
|
button_frame = ttk.Frame(top_nav_frame)
|
|
button_frame.pack(side=tk.LEFT, padx=5)
|
|
button = ttk.Button(button_frame, text=text,
|
|
command=command, style="TButton.Borderless.Round")
|
|
button.pack(side=tk.TOP)
|
|
self.nav_buttons.append(button)
|
|
progress_bar = ttk.Progressbar(
|
|
button_frame, orient="horizontal", length=50, mode="determinate", style="Small.Horizontal.TProgressbar")
|
|
progress_bar.pack_forget()
|
|
self.nav_progress_bars.append(progress_bar)
|
|
|
|
if i < len(self.nav_buttons_defs) - 1:
|
|
ttk.Separator(top_nav_frame, orient=tk.VERTICAL).pack(
|
|
side=tk.LEFT, fill=tk.Y, padx=2)
|
|
|
|
view_container = ttk.Frame(self)
|
|
view_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
self.tree_frame = ttk.LabelFrame(
|
|
view_container, text=Msg.STR["exclude_system_folders"], padding=10)
|
|
|
|
columns = ("included", "name", "path")
|
|
self.tree = ttk.Treeview(
|
|
self.tree_frame, columns=columns, show="headings")
|
|
self.tree.heading("included", text=Msg.STR["in_backup"])
|
|
self.tree.heading("name", text=Msg.STR["name"])
|
|
self.tree.heading("path", text=Msg.STR["path"])
|
|
self.tree.column("path", anchor="center")
|
|
self.tree.column("name", anchor="center")
|
|
self.tree.column("included", width=100, anchor="center")
|
|
self.tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
self.tree.tag_configure("backup_dest_exclude", foreground="gray")
|
|
self.tree.bind("<Button-1>", self._toggle_include_status)
|
|
|
|
self.info_label = ttk.Label(
|
|
self, text=Msg.STR["advanced_settings_warning"], wraplength=780, justify="left")
|
|
self.info_label.pack(pady=10, fill=tk.X, padx=10)
|
|
|
|
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)
|
|
|
|
self.animation_settings_frame = ttk.LabelFrame(
|
|
view_container, text=Msg.STR["animation_settings_title"], padding=10)
|
|
|
|
animation_types = ["counter_arc", "double_arc", "line", "blink"]
|
|
|
|
ttk.Label(self.animation_settings_frame, text=Msg.STR["backup_animation_label"]).grid(
|
|
row=0, column=0, sticky="w", pady=2)
|
|
self.backup_anim_var = tk.StringVar()
|
|
self.backup_anim_combo = ttk.Combobox(
|
|
self.animation_settings_frame, textvariable=self.backup_anim_var, values=animation_types, state="readonly")
|
|
self.backup_anim_combo.grid(row=0, column=1, sticky="ew", padx=5)
|
|
|
|
ttk.Label(self.animation_settings_frame, text=Msg.STR["calc_animation_label"]).grid(
|
|
row=1, column=0, sticky="w", pady=2)
|
|
self.calc_anim_var = tk.StringVar()
|
|
self.calc_anim_combo = ttk.Combobox(
|
|
self.animation_settings_frame, textvariable=self.calc_anim_var, values=animation_types, state="readonly")
|
|
self.calc_anim_combo.grid(row=1, column=1, sticky="ew", padx=5)
|
|
|
|
reset_button = ttk.Button(
|
|
self.animation_settings_frame, text=Msg.STR["default_settings"], command=self._reset_animation_settings)
|
|
reset_button.grid(row=0, column=2, rowspan=2, padx=10)
|
|
|
|
self.animation_settings_frame.columnconfigure(1, weight=1)
|
|
|
|
self.backup_defaults_frame = ttk.LabelFrame(
|
|
view_container, text=Msg.STR["backup_defaults_title"], padding=10)
|
|
|
|
self.force_full_var = tk.BooleanVar()
|
|
self.force_incremental_var = tk.BooleanVar()
|
|
self.force_compression_var = tk.BooleanVar()
|
|
self.force_encryption_var = tk.BooleanVar()
|
|
self.use_trash_bin_var = tk.BooleanVar()
|
|
self.no_trash_bin_var = tk.BooleanVar()
|
|
|
|
self.full_backup_checkbutton = ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_full_backup"], variable=self.force_full_var, command=lambda: enforce_backup_type_exclusivity(
|
|
self.force_full_var, self.force_incremental_var, self.force_full_var.get()), style="Switch.TCheckbutton")
|
|
self.full_backup_checkbutton.pack(anchor=tk.W)
|
|
|
|
self.incremental_checkbutton = ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_incremental_backup"], variable=self.force_incremental_var, command=lambda: enforce_backup_type_exclusivity(
|
|
self.force_incremental_var, self.force_full_var, self.force_incremental_var.get()), style="Switch.TCheckbutton")
|
|
self.incremental_checkbutton.pack(anchor=tk.W)
|
|
|
|
self.compression_checkbutton = ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_compression"], variable=self.force_compression_var, command=self._on_compression_toggle, style="Switch.TCheckbutton")
|
|
self.compression_checkbutton.pack(anchor=tk.W)
|
|
|
|
self.encryption_checkbutton = ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_encryption"], variable=self.force_encryption_var, style="Switch.TCheckbutton")
|
|
self.encryption_checkbutton.pack(anchor=tk.W)
|
|
|
|
ttk.Separator(self.backup_defaults_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
trash_info_label = ttk.Label(self.backup_defaults_frame, text=Msg.STR["trash_bin_explanation"], wraplength=750, justify="left")
|
|
trash_info_label.pack(anchor=tk.W, pady=5)
|
|
|
|
ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["use_trash_bin"], variable=self.use_trash_bin_var, command=lambda: self._handle_trash_checkbox_click(
|
|
self.use_trash_bin_var, self.no_trash_bin_var), style="Switch.TCheckbutton").pack(anchor=tk.W)
|
|
ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["no_trash_bin"], variable=self.no_trash_bin_var, command=lambda: self._handle_trash_checkbox_click(
|
|
self.no_trash_bin_var, self.use_trash_bin_var), style="Switch.TCheckbutton").pack(anchor=tk.W)
|
|
|
|
ttk.Separator(self.backup_defaults_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
encryption_note = ttk.Label(
|
|
self.backup_defaults_frame, text=Msg.STR["encryption_note_system_backup"], wraplength=750, justify="left")
|
|
encryption_note.pack(anchor=tk.W, pady=5)
|
|
|
|
self.keyfile_settings_frame = ttk.LabelFrame(
|
|
view_container, text=Msg.STR["automation_settings_title"], padding=10)
|
|
|
|
key_file_button = ttk.Button(
|
|
self.keyfile_settings_frame, text=Msg.STR["create_add_key_file"], command=self._create_key_file)
|
|
key_file_button.grid(row=0, column=0, padx=5, pady=5)
|
|
|
|
self.key_file_status_var = tk.StringVar(
|
|
value=Msg.STR["key_file_not_created"])
|
|
key_file_status_label = ttk.Label(
|
|
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_label = ttk.Label(
|
|
self.keyfile_settings_frame, text=sudoers_info_text, justify="left")
|
|
|
|
sudoers_info_label.grid(
|
|
row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5)
|
|
|
|
self.keyfile_settings_frame.columnconfigure(1, weight=1)
|
|
|
|
button_frame = ttk.Frame(self)
|
|
button_frame.pack(pady=10)
|
|
|
|
ttk.Button(button_frame, text=Msg.STR["apply"], command=self._apply_changes).pack(
|
|
side=tk.LEFT, padx=5)
|
|
ttk.Button(button_frame, text=Msg.STR["cancel"], command=self._cancel_changes).pack(
|
|
side=tk.LEFT, padx=5)
|
|
|
|
self.tree_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.manual_excludes_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.keyfile_settings_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.animation_settings_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.backup_defaults_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
self._load_system_folders()
|
|
self._load_animation_settings()
|
|
self._load_backup_defaults()
|
|
self._load_manual_excludes()
|
|
self._update_key_file_status()
|
|
|
|
self._switch_view(self.current_view_index)
|
|
|
|
def _on_compression_toggle(self):
|
|
if self.force_compression_var.get():
|
|
self.force_incremental_var.set(False)
|
|
self.incremental_checkbutton.config(state="disabled")
|
|
|
|
self.force_encryption_var.set(False)
|
|
self.encryption_checkbutton.config(state="disabled")
|
|
else:
|
|
self.incremental_checkbutton.config(state="normal")
|
|
self.encryption_checkbutton.config(state="normal")
|
|
|
|
def _handle_trash_checkbox_click(self, changed_var, other_var):
|
|
enforce_backup_type_exclusivity(changed_var, other_var, changed_var.get())
|
|
self._on_trash_setting_change()
|
|
|
|
def _on_trash_setting_change(self):
|
|
use_trash = self.use_trash_bin_var.get()
|
|
no_trash = self.no_trash_bin_var.get()
|
|
|
|
if no_trash:
|
|
self.app_instance.sync_mode_label.config(
|
|
text=Msg.STR["sync_mode_pure_sync"], foreground="red")
|
|
elif use_trash:
|
|
self.app_instance.sync_mode_label.config(
|
|
text=Msg.STR["sync_mode_trash_bin"], foreground="orange")
|
|
else:
|
|
self.app_instance.sync_mode_label.config(
|
|
text=Msg.STR["sync_mode_no_delete"], foreground="green")
|
|
|
|
self.config_manager.set_setting("use_trash_bin", use_trash)
|
|
self.config_manager.set_setting("no_trash_bin", no_trash)
|
|
|
|
def _create_key_file(self):
|
|
if not self.app_instance.destination_path:
|
|
MessageDialog(self, message_type="error", title="Error",
|
|
text="Please select a backup destination first.")
|
|
return
|
|
|
|
pybackup_dir = os.path.join(
|
|
self.app_instance.destination_path, "pybackup")
|
|
container_path = os.path.join(pybackup_dir, "pybackup_encrypted.luks")
|
|
if not os.path.exists(container_path):
|
|
MessageDialog(self, message_type="error", title="Error",
|
|
text="No encrypted container found at the destination.")
|
|
return
|
|
|
|
password_dialog = PasswordDialog(
|
|
self, title="Enter Existing Password", confirm=False)
|
|
password, _ = password_dialog.get_password()
|
|
|
|
if not password:
|
|
return
|
|
|
|
key_file_path = self.app_instance.backup_manager.encryption_manager.create_and_add_key_file(
|
|
self.app_instance.destination_path, password)
|
|
|
|
if key_file_path:
|
|
MessageDialog(self, message_type="info", title="Success",
|
|
text=f"Key file created and added successfully!\nPath: {key_file_path}")
|
|
else:
|
|
MessageDialog(self, message_type="error", title="Error",
|
|
text="Failed to create or add key file. See log for details.")
|
|
|
|
self._update_key_file_status()
|
|
|
|
def _update_key_file_status(self):
|
|
if not self.app_instance.destination_path:
|
|
self.key_file_status_var.set(
|
|
"Key file status unknown (no destination set).")
|
|
return
|
|
|
|
key_file_path = self.app_instance.backup_manager.encryption_manager.get_key_file_path(
|
|
self.app_instance.destination_path)
|
|
if os.path.exists(key_file_path):
|
|
self.key_file_status_var.set(f"Key file exists: {key_file_path}")
|
|
else:
|
|
self.key_file_status_var.set(
|
|
"Key file has not been created for this destination.")
|
|
|
|
def _switch_view(self, index):
|
|
self.current_view_index = index
|
|
self.update_nav_buttons(index)
|
|
|
|
self.tree_frame.pack_forget()
|
|
self.manual_excludes_frame.pack_forget()
|
|
self.keyfile_settings_frame.pack_forget()
|
|
self.animation_settings_frame.pack_forget()
|
|
self.backup_defaults_frame.pack_forget()
|
|
|
|
if index == 0:
|
|
self.tree_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.info_label.config(text=Msg.STR["advanced_settings_warning"])
|
|
elif index == 1:
|
|
self.manual_excludes_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.info_label.config(text=Msg.STR["manual_excludes_info"])
|
|
elif index == 2:
|
|
self.keyfile_settings_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.info_label.config(text="")
|
|
elif index == 3:
|
|
self.animation_settings_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.info_label.config(text="")
|
|
elif index == 4:
|
|
self.backup_defaults_frame.pack(fill=tk.BOTH, expand=True)
|
|
self.info_label.config(text="")
|
|
|
|
def update_nav_buttons(self, active_index):
|
|
for i, button in enumerate(self.nav_buttons):
|
|
if i == active_index:
|
|
button.configure(style="Toolbutton")
|
|
self.nav_progress_bars[i].pack(side=tk.BOTTOM, fill=tk.X)
|
|
self.nav_progress_bars[i]['value'] = 100
|
|
else:
|
|
button.configure(style="Gray.Toolbutton")
|
|
self.nav_progress_bars[i].pack_forget()
|
|
|
|
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")
|
|
self.config_manager.remove_setting("calculation_animation_type")
|
|
self._load_animation_settings()
|
|
|
|
def _reset_backup_defaults(self):
|
|
self.config_manager.remove_setting("force_full_backup")
|
|
self.config_manager.remove_setting("force_incremental_backup")
|
|
self.config_manager.remove_setting("force_compression")
|
|
self.config_manager.remove_setting("force_encryption")
|
|
self._load_backup_defaults()
|
|
if self.app_instance:
|
|
self.app_instance.update_backup_options_from_config()
|
|
|
|
def _load_backup_defaults(self):
|
|
self.force_full_var.set(
|
|
self.config_manager.get_setting("force_full_backup", False))
|
|
self.force_incremental_var.set(
|
|
self.config_manager.get_setting("force_incremental_backup", False))
|
|
self.force_compression_var.set(
|
|
self.config_manager.get_setting("force_compression", False))
|
|
self.force_encryption_var.set(
|
|
self.config_manager.get_setting("force_encryption", False))
|
|
self.use_trash_bin_var.set(
|
|
self.config_manager.get_setting("use_trash_bin", False))
|
|
self.no_trash_bin_var.set(
|
|
self.config_manager.get_setting("no_trash_bin", False))
|
|
self._on_compression_toggle()
|
|
|
|
def _load_animation_settings(self):
|
|
backup_anim = self.config_manager.get_setting(
|
|
"backup_animation_type", "counter_arc")
|
|
calc_anim = self.config_manager.get_setting(
|
|
"calculation_animation_type", "double_arc")
|
|
self.backup_anim_var.set(backup_anim)
|
|
self.calc_anim_var.set(calc_anim)
|
|
|
|
def _load_system_folders(self):
|
|
for i in self.tree.get_children():
|
|
self.tree.delete(i)
|
|
|
|
always_exclude = [
|
|
"/home", "/root", "/bin", "/etc", "/lib", "/lib64", "/sys", "/sbin", "/usr",
|
|
"/tmp", "/dev", "/run", "/mnt", "/proc", "/media", "/cdrom", "/var",
|
|
"/sbin.usr-is-merged", "/bin.usr-is-merged", "/lib.usr-is-merged", "/boot"
|
|
]
|
|
always_exclude.extend(AppConfig.STANDARD_EXCLUDE_CONTENT.splitlines())
|
|
_, user_patterns = self._load_exclude_patterns()
|
|
items_to_display = {}
|
|
|
|
root_dir = Path("/")
|
|
for item in root_dir.iterdir():
|
|
if item.is_dir():
|
|
item_path_str = str(item.absolute())
|
|
if item_path_str in always_exclude or f"{item_path_str}/*" in always_exclude:
|
|
continue
|
|
is_user_excluded = f"{item_path_str}/*" in user_patterns
|
|
included_text = Msg.STR["no"] if is_user_excluded else Msg.STR["yes"]
|
|
items_to_display[item_path_str] = (
|
|
included_text, item.name, item_path_str)
|
|
|
|
if self.app_instance and self.app_instance.destination_path:
|
|
backup_root_path = Path(
|
|
f"/{self.app_instance.destination_path.strip('/').split('/')[0]}")
|
|
backup_root_path_str = str(backup_root_path.absolute())
|
|
items_to_display[backup_root_path_str] = (
|
|
Msg.STR["no"], backup_root_path.name, backup_root_path_str)
|
|
|
|
restore_src_path = self.config_manager.get_setting(
|
|
"restore_source_path")
|
|
if restore_src_path and Path(restore_src_path).is_dir():
|
|
restore_root_path = Path(
|
|
f"/{str(Path(restore_src_path)).strip('/').split('/')[0]}")
|
|
restore_root_path_str = str(restore_root_path.absolute())
|
|
items_to_display[restore_root_path_str] = (
|
|
Msg.STR["no"], restore_root_path.name, restore_root_path_str)
|
|
|
|
for item_path_str in sorted(items_to_display.keys()):
|
|
item_values = items_to_display[item_path_str]
|
|
tag = "yes" if item_values[0] == Msg.STR["yes"] else "no"
|
|
|
|
is_backup_dest = (self.app_instance and self.app_instance.destination_path and
|
|
item_path_str == str(Path(f"/{self.app_instance.destination_path.strip('/').split('/')[0]}").absolute()))
|
|
is_restore_src = (restore_src_path and
|
|
item_path_str == str(Path(f"/{str(Path(restore_src_path)).strip('/').split('/')[0]}").absolute()))
|
|
|
|
if is_backup_dest or is_restore_src:
|
|
tags = ("backup_dest_exclude", tag)
|
|
else:
|
|
tags = (tag,)
|
|
self.tree.insert("", "end", values=item_values, tags=tags)
|
|
|
|
def _toggle_include_status(self, event):
|
|
item_id = self.tree.identify_row(event.y)
|
|
if not item_id:
|
|
return
|
|
if "backup_dest_exclude" in self.tree.item(item_id, "tags"):
|
|
return
|
|
current_values = self.tree.item(item_id, 'values')
|
|
new_status = Msg.STR["yes"] if current_values[0] == Msg.STR["no"] else Msg.STR["no"]
|
|
|
|
new_tag = "yes" if new_status == Msg.STR["yes"] else "no"
|
|
|
|
self.tree.item(item_id, values=(
|
|
new_status, current_values[1], current_values[2]), tags=(new_tag,))
|
|
|
|
def _apply_changes(self):
|
|
self.config_manager.set_setting(
|
|
"backup_animation_type", self.backup_anim_var.get())
|
|
self.config_manager.set_setting(
|
|
"calculation_animation_type", self.calc_anim_var.get())
|
|
self.config_manager.set_setting(
|
|
"force_full_backup", self.force_full_var.get())
|
|
self.config_manager.set_setting(
|
|
"force_incremental_backup", self.force_incremental_var.get())
|
|
self.config_manager.set_setting(
|
|
"force_compression", self.force_compression_var.get())
|
|
self.config_manager.set_setting(
|
|
"force_encryption", self.force_encryption_var.get())
|
|
self.config_manager.set_setting("use_trash_bin", self.use_trash_bin_var.get())
|
|
self.config_manager.set_setting("no_trash_bin", self.no_trash_bin_var.get())
|
|
|
|
if self.app_instance:
|
|
self.app_instance.update_backup_options_from_config()
|
|
self.app_instance.animated_icon.destroy()
|
|
|
|
bg_color = self.app_instance.style.lookup('TFrame', 'background')
|
|
backup_animation_type = self.backup_anim_var.get()
|
|
|
|
initial_animation_type = "blink"
|
|
if backup_animation_type == "line":
|
|
initial_animation_type = "line"
|
|
|
|
self.app_instance.animated_icon = AnimatedIcon(
|
|
self.app_instance.action_frame, width=20, height=20, use_pillow=True, bg=bg_color, animation_type=initial_animation_type)
|
|
|
|
self.app_instance.animated_icon.grid(row=0, column=0, rowspan=2, padx=5)
|
|
|
|
self.app_instance.animated_icon.stop("DISABLE")
|
|
self.app_instance.animated_icon.animation_type = backup_animation_type
|
|
|
|
tree_paths = set()
|
|
for item_id in self.tree.get_children():
|
|
values = self.tree.item(item_id, 'values')
|
|
tree_paths.add(values[2])
|
|
|
|
new_excludes = []
|
|
for item_id in self.tree.get_children():
|
|
values = self.tree.item(item_id, 'values')
|
|
if values[0] == Msg.STR["no"]:
|
|
path = values[2]
|
|
if os.path.isdir(path):
|
|
new_excludes.append(f"{path}/*")
|
|
else:
|
|
new_excludes.append(path)
|
|
|
|
existing_patterns = []
|
|
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
|
|
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'r') as f:
|
|
existing_patterns = [
|
|
line.strip() for line in f if line.strip() and not line.startswith('#')]
|
|
|
|
preserved_patterns = []
|
|
for pattern in existing_patterns:
|
|
clean_pattern = pattern.replace('/*', '')
|
|
if clean_pattern not in tree_paths:
|
|
preserved_patterns.append(pattern)
|
|
|
|
final_excludes = list(set(preserved_patterns + new_excludes))
|
|
|
|
if self.app_instance and self.app_instance.destination_path:
|
|
backup_root_to_exclude = f"/{self.app_instance.destination_path.strip('/').split('/')[0]}/*"
|
|
if backup_root_to_exclude not in final_excludes:
|
|
final_excludes.append(backup_root_to_exclude)
|
|
|
|
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'w') as f:
|
|
for path in final_excludes:
|
|
f.write(f"{path}\n")
|
|
|
|
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.pack_forget()
|
|
if self.show_main_settings_callback:
|
|
self.show_main_settings_callback()
|
|
|
|
if self.app_instance:
|
|
current_source = self.app_instance.left_canvas_data.get('folder')
|
|
if current_source:
|
|
self.app_instance.actions.on_sidebar_button_click(
|
|
current_source)
|
|
|
|
def _cancel_changes(self):
|
|
self.pack_forget()
|
|
if self.show_main_settings_callback:
|
|
self.show_main_settings_callback()
|
|
|
|
def _load_exclude_patterns(self):
|
|
generated_patterns = []
|
|
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():
|
|
with open(AppConfig.GENERATED_EXCLUDE_LIST_PATH, 'r') as f:
|
|
generated_patterns = [
|
|
line.strip() for line in f if line.strip() and not line.startswith('#')]
|
|
|
|
user_patterns = []
|
|
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
|
|
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'r') as f:
|
|
user_patterns.extend(
|
|
[line.strip() for line in f if line.strip() and not line.startswith('#')])
|
|
|
|
if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists():
|
|
with open(AppConfig.MANUAL_EXCLUDE_LIST_PATH, 'r') as f:
|
|
user_patterns.extend(
|
|
[line.strip() for line in f if line.strip() and not line.startswith('#')])
|
|
|
|
return generated_patterns, user_patterns
|