feat: Implement backup option logic and UI improvements
This commit introduces the following changes:
- Enhanced Backup Option Logic:
- Implemented mutual exclusivity between 'Compressed' and 'Incremental' backups:
- If 'Compressed' is selected, 'Incremental' is deselected and disabled, and 'Full' is automatically selected.
- Implemented mutual exclusivity between 'Compressed' and 'Encrypted' backups:
- If 'Compressed' is selected, 'Encrypted' is deselected and disabled.
- If 'Encrypted' is selected, 'Compressed' is deselected and disabled.
- If 'Incremental' is selected, 'Compressed' is deselected and disabled.
- These rules are applied consistently across the main backup window, advanced settings, and scheduler.
- UI Improvements:
- Added 'Full', 'Incremental', 'Compressed', and 'Encrypted' checkboxes to the scheduler view.
- Adjusted the layout in the scheduler view to place 'Backup Options' next to 'Folders to back up'.
- Added missing string definitions for new UI elements in core/pbp_app_config.py.
- Refactoring:
- Updated _refresh_backup_options_ui in pyimage_ui/actions.py to handle the new logic.
- Modified _on_compression_toggle in pyimage_ui/advanced_settings_frame.py and _on_compression_toggle_scheduler in pyimage_ui/scheduler_frame.py to reflect the updated exclusivity rules.
- Adjusted _save_job and _load_scheduled_jobs in pyimage_ui/scheduler_frame.py to include and parse the new backup options.
- Updated _parse_job_comment in core/backup_manager.py to correctly parse new backup options from cron job comments.
This commit is contained in:
@@ -255,6 +255,7 @@ class Msg:
|
||||
"log": _("Log"),
|
||||
"full_backup": _("Full backup"),
|
||||
"incremental": _("Incremental"),
|
||||
"incremental_backup": _("Incremental backup"), # New
|
||||
"test_run": _("Test run"),
|
||||
"start": _("Start"),
|
||||
"cancel_backup": _("Cancel"),
|
||||
@@ -345,7 +346,9 @@ class Msg:
|
||||
"header_subtitle": _("Simple GUI for rsync"),
|
||||
"encrypted_backup_content": _("Encrypted Backups"),
|
||||
"compressed": _("Compressed"),
|
||||
"compression": _("Compression"), # New
|
||||
"encrypted": _("Encrypted"),
|
||||
"encryption": _("Encryption"), # New
|
||||
"bypass_security": _("Bypass security"),
|
||||
"comment": _("Kommentar"),
|
||||
"force_full_backup": _("Always force full backup"),
|
||||
@@ -364,4 +367,5 @@ class Msg:
|
||||
"automation_settings_title": _("Automation Settings"), # New
|
||||
"create_add_key_file": _("Create/Add Key File"), # New
|
||||
"key_file_not_created": _("Key file not created."), # New
|
||||
"backup_options": _("Backup Options"), # New
|
||||
}
|
||||
|
||||
@@ -68,26 +68,30 @@ class Actions:
|
||||
self._set_backup_type("full")
|
||||
|
||||
def _refresh_backup_options_ui(self):
|
||||
# Reset all to normal state first
|
||||
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")
|
||||
|
||||
if self.app.encrypted_var.get():
|
||||
self.app.compressed_var.set(False)
|
||||
# Apply specific logic based on current states
|
||||
if self.app.compressed_var.get():
|
||||
self.app.inkrementell_var.set(False) # If compressed, cannot be incremental
|
||||
self.app.vollbackup_var.set(True) # Force full if compressed
|
||||
self.app.incremental_cb.config(state="disabled")
|
||||
self.app.encrypted_var.set(False) # If compressed, cannot be encrypted
|
||||
self.app.encrypted_cb.config(state="disabled")
|
||||
self.app.accurate_size_cb.config(state="disabled") # Accurate size not applicable for compressed
|
||||
self.app.genaue_berechnung_var.set(False)
|
||||
elif self.app.encrypted_var.get():
|
||||
self.app.compressed_var.set(False) # If encrypted, cannot be compressed
|
||||
self.app.compressed_cb.config(state="disabled")
|
||||
elif self.app.inkrementell_var.get():
|
||||
self.app.compressed_var.set(False) # If incremental, cannot be compressed
|
||||
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")
|
||||
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)
|
||||
|
||||
# Update backup type controls (full/incremental) based on current state
|
||||
self._update_backup_type_controls()
|
||||
|
||||
def handle_backup_type_change(self, changed_var_name):
|
||||
|
||||
@@ -125,14 +125,19 @@ class AdvancedSettingsFrame(ttk.Frame):
|
||||
self.use_trash_bin_var = tk.BooleanVar()
|
||||
self.no_trash_bin_var = tk.BooleanVar()
|
||||
|
||||
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())).pack(anchor=tk.W)
|
||||
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())).pack(anchor=tk.W)
|
||||
ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_compression"], variable=self.force_compression_var, command=lambda: enforce_backup_type_exclusivity(
|
||||
self.force_compression_var, self.force_encryption_var, self.force_compression_var.get())).pack(anchor=tk.W)
|
||||
ttk.Checkbutton(self.backup_defaults_frame, text=Msg.STR["force_encryption"], variable=self.force_encryption_var, command=lambda: enforce_backup_type_exclusivity(
|
||||
self.force_encryption_var, self.force_compression_var, self.force_encryption_var.get())).pack(anchor=tk.W)
|
||||
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()))
|
||||
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()))
|
||||
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)
|
||||
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)
|
||||
self.encryption_checkbutton.pack(anchor=tk.W)
|
||||
|
||||
ttk.Separator(self.backup_defaults_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
||||
|
||||
@@ -196,6 +201,17 @@ class AdvancedSettingsFrame(ttk.Frame):
|
||||
|
||||
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()
|
||||
@@ -341,6 +357,7 @@ class AdvancedSettingsFrame(ttk.Frame):
|
||||
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(
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
from shared_libs.custom_file_dialog import CustomFileDialog
|
||||
from shared_libs.message import MessageDialog
|
||||
from core.pbp_app_config import Msg
|
||||
from pyimage_ui.shared_logic import enforce_backup_type_exclusivity
|
||||
|
||||
|
||||
class SchedulerFrame(ttk.Frame):
|
||||
@@ -17,7 +18,7 @@ class SchedulerFrame(ttk.Frame):
|
||||
self, text=Msg.STR["scheduled_jobs"], padding=10)
|
||||
self.jobs_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
columns = ("active", "type", "frequency", "destination", "sources")
|
||||
columns = ("active", "type", "frequency", "destination", "sources", "options")
|
||||
self.jobs_tree = ttk.Treeview(
|
||||
self.jobs_frame, columns=columns, show="headings")
|
||||
self.jobs_tree.heading("active", text=Msg.STR["active"])
|
||||
@@ -25,6 +26,7 @@ class SchedulerFrame(ttk.Frame):
|
||||
self.jobs_tree.heading("frequency", text=Msg.STR["frequency"])
|
||||
self.jobs_tree.heading("destination", text=Msg.STR["destination"])
|
||||
self.jobs_tree.heading("sources", text=Msg.STR["sources"])
|
||||
self.jobs_tree.heading("options", text=Msg.STR["backup_options"])
|
||||
for col in columns:
|
||||
self.jobs_tree.column(col, width=100, anchor="center")
|
||||
self.jobs_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
@@ -59,13 +61,37 @@ class SchedulerFrame(ttk.Frame):
|
||||
ttk.Radiobutton(type_frame, text=Msg.STR["user_backup_menu"], variable=self.backup_type,
|
||||
value="user", command=self._toggle_user_sources).pack(anchor=tk.W)
|
||||
|
||||
# Container for source folders and backup options
|
||||
source_options_container = ttk.Frame(self.add_job_frame)
|
||||
source_options_container.pack(fill=tk.X, padx=5, pady=5)
|
||||
source_options_container.columnconfigure(0, weight=1)
|
||||
source_options_container.columnconfigure(1, weight=1)
|
||||
|
||||
self.user_sources_frame = ttk.LabelFrame(
|
||||
self.add_job_frame, text=Msg.STR["source_folders"], padding=10)
|
||||
self.user_sources_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||
source_options_container, text=Msg.STR["source_folders"], padding=10)
|
||||
self.user_sources_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
||||
for name, var in self.user_sources.items():
|
||||
ttk.Checkbutton(self.user_sources_frame, text=name,
|
||||
variable=var).pack(anchor=tk.W)
|
||||
|
||||
options_frame = ttk.LabelFrame(
|
||||
source_options_container, text=Msg.STR["backup_options"], padding=10)
|
||||
options_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
|
||||
|
||||
self.full_var = tk.BooleanVar(value=True)
|
||||
self.incremental_var = tk.BooleanVar(value=False)
|
||||
self.compress_var = tk.BooleanVar(value=False)
|
||||
self.encrypt_var = tk.BooleanVar(value=False)
|
||||
|
||||
self.full_checkbutton = ttk.Checkbutton(options_frame, text=Msg.STR["full_backup"], variable=self.full_var, command=lambda: enforce_backup_type_exclusivity(self.full_var, self.incremental_var, self.full_var.get()))
|
||||
self.full_checkbutton.pack(anchor=tk.W)
|
||||
self.incremental_checkbutton = ttk.Checkbutton(options_frame, text=Msg.STR["incremental_backup"], variable=self.incremental_var, command=lambda: enforce_backup_type_exclusivity(self.incremental_var, self.full_var, self.incremental_var.get()))
|
||||
self.incremental_checkbutton.pack(anchor=tk.W)
|
||||
self.compress_checkbutton = ttk.Checkbutton(options_frame, text=Msg.STR["compression"], variable=self.compress_var, command=self._on_compression_toggle_scheduler)
|
||||
self.compress_checkbutton.pack(anchor=tk.W)
|
||||
self.encrypt_checkbutton = ttk.Checkbutton(options_frame, text=Msg.STR["encryption"], variable=self.encrypt_var)
|
||||
self.encrypt_checkbutton.pack(anchor=tk.W)
|
||||
|
||||
dest_frame = ttk.LabelFrame(
|
||||
self.add_job_frame, text=Msg.STR["dest_folder"], padding=10)
|
||||
dest_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||
@@ -104,13 +130,34 @@ class SchedulerFrame(ttk.Frame):
|
||||
def hide(self):
|
||||
self.grid_remove()
|
||||
|
||||
def _on_compression_toggle_scheduler(self):
|
||||
if self.compress_var.get():
|
||||
self.incremental_var.set(False)
|
||||
self.incremental_checkbutton.config(state="disabled")
|
||||
|
||||
self.encrypt_var.set(False)
|
||||
self.encrypt_checkbutton.config(state="disabled")
|
||||
else:
|
||||
self.incremental_checkbutton.config(state="normal")
|
||||
self.encrypt_checkbutton.config(state="normal")
|
||||
|
||||
def _toggle_scheduler_view(self):
|
||||
if self.jobs_frame.winfo_ismapped():
|
||||
self.jobs_frame.pack_forget()
|
||||
self.add_job_frame.pack(fill=tk.BOTH, expand=True)
|
||||
# Reset or load default values for new job
|
||||
self.full_var.set(True)
|
||||
self.incremental_var.set(False)
|
||||
self.compress_var.set(False)
|
||||
self.encrypt_var.set(False)
|
||||
self.destination.set("")
|
||||
for var in self.user_sources.values():
|
||||
var.set(False)
|
||||
self._on_compression_toggle_scheduler() # Update state of incremental checkbox
|
||||
else:
|
||||
self.add_job_frame.pack_forget()
|
||||
self.jobs_frame.pack(fill=tk.BOTH, expand=True)
|
||||
self._load_scheduled_jobs()
|
||||
|
||||
def _toggle_user_sources(self):
|
||||
state = "normal" if self.backup_type.get() == "user" else "disabled"
|
||||
@@ -144,17 +191,29 @@ class SchedulerFrame(ttk.Frame):
|
||||
return
|
||||
|
||||
script_path = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "..", "main_app.py"))
|
||||
os.path.dirname(__file__), "..", "pybackup-cli.py"))
|
||||
command = f"python3 {script_path} --backup-type {job_type} --destination \"{dest}\""
|
||||
|
||||
if self.full_var.get():
|
||||
command += " --full"
|
||||
if self.incremental_var.get():
|
||||
command += " --incremental"
|
||||
if self.compress_var.get():
|
||||
command += " --compress"
|
||||
if self.encrypt_var.get():
|
||||
command += " --encrypt"
|
||||
|
||||
if job_type == "user":
|
||||
command += f" --sources "
|
||||
for s in job_sources:
|
||||
command += f'\"{s}\" ' # This line has an issue with escaping
|
||||
command += f'\"{s}\" '
|
||||
|
||||
comment = f"{self.backup_manager.app_tag}; type:{job_type}; freq:{job_frequency}; dest:{dest}"
|
||||
if job_type == "user":
|
||||
comment += f"; sources:{','.join(job_sources)}"
|
||||
|
||||
comment += f"; full:{self.full_var.get()}; incremental:{self.incremental_var.get()}; compress:{self.compress_var.get()}; encrypt:{self.encrypt_var.get()}"
|
||||
|
||||
job_details = {
|
||||
"command": command,
|
||||
"comment": comment,
|
||||
@@ -172,9 +231,19 @@ class SchedulerFrame(ttk.Frame):
|
||||
self.jobs_tree.delete(i)
|
||||
jobs = self.backup_manager.get_scheduled_jobs()
|
||||
for job in jobs:
|
||||
options = []
|
||||
if job.get("full"):
|
||||
options.append("Full")
|
||||
if job.get("incremental"):
|
||||
options.append("Incremental")
|
||||
if job.get("compress"):
|
||||
options.append("Compressed")
|
||||
if job.get("encrypt"):
|
||||
options.append("Encrypted")
|
||||
|
||||
self.jobs_tree.insert("", "end", values=(
|
||||
job["active"], job["type"], job["frequency"], job["destination"], ", ".join(
|
||||
job["sources"])
|
||||
job["sources"]), ", ".join(options)
|
||||
), iid=job["id"])
|
||||
|
||||
def _remove_selected_job(self):
|
||||
|
||||
Reference in New Issue
Block a user