refactor: Rework encrypted backup UI and logic

- Centralizes backup content view logic into a single BackupContentFrame.
- Removes the separate, now obsolete EncryptedBackupContentFrame.
- Adds a toggle button within the BackupContentFrame to switch between viewing normal and encrypted backups.
- Centralizes the Restore, Delete, and Edit Comment buttons into a single button bar in BackupContentFrame.
- Corrects the path resolution logic to find backups and encrypted containers within the /pybackup subdirectory.
- Fixes UI bugs where action buttons would disappear when switching tabs.
This commit is contained in:
2025-09-04 23:22:12 +02:00
parent 0b9c58410f
commit 2d685e1d97
10 changed files with 336 additions and 480 deletions

View File

@@ -582,54 +582,16 @@ set -e
backups.append(item)
return sorted(backups, reverse=True)
def list_system_backups(self, base_backup_path: str) -> List[Dict[str, str]]:
"""Lists all system backups found in the pybackup subdirectory."""
system_backups = []
pybackup_path = os.path.join(base_backup_path, "pybackup")
def list_system_backups(self, scan_path: str, is_encrypted_and_mounted: bool = False) -> List[Dict[str, str]]:
"""Lists all system backups, handling encrypted containers and sorting into groups."""
if not os.path.isdir(scan_path):
return []
if not os.path.isdir(pybackup_path):
return system_backups
# Check for a single encrypted container first
encrypted_container_path = os.path.join(pybackup_path, "pybackup_encrypted.luks")
if os.path.exists(encrypted_container_path):
try:
stat_info = os.stat(encrypted_container_path)
m_time = datetime.datetime.fromtimestamp(stat_info.st_mtime)
file_size = stat_info.st_size
# Format size
power = 1024
n = 0
power_labels = {0: 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'}
display_size = file_size
while display_size >= power and n < len(power_labels) - 1:
display_size /= power
n += 1
size_str = f"{display_size:.2f} {power_labels[n]}"
system_backups.append({
"date": m_time.strftime('%d-%m-%Y'),
"time": m_time.strftime('%H:%M:%S'),
"type": "Encrypted Container",
"size": size_str,
"folder_name": "pybackup_encrypted.luks",
"full_path": encrypted_container_path,
"comment": "Container for all encrypted backups",
"is_compressed": False,
"is_encrypted": True,
"backup_type_base": "Full" # Treat container as a single full entity
})
return system_backups # Return immediately
except Exception as e:
self.logger.log(f"Could not stat encrypted container file: {e}")
# Fall through to normal processing if stat fails
# Proceed with individual folder scan if no container is found
all_backups = []
name_regex = re.compile(
r"^(\d{2}-\d{2}-\d{4})_(\d{2}:\d{2}:\d{2})_system_(full|incremental)(\.tar\.gz)?$", re.IGNORECASE)
for item in os.listdir(pybackup_path):
for item in os.listdir(scan_path):
if item.endswith('.txt') or item.endswith('.luks'):
continue
@@ -637,39 +599,29 @@ set -e
if not match:
continue
full_path = os.path.join(pybackup_path, item)
date_str = match.group(1)
time_str = match.group(2)
backup_type_base = match.group(3).capitalize()
extension = match.group(4)
full_path = os.path.join(scan_path, item)
date_str, time_str, backup_type_base, extension = match.groups()
is_compressed = (extension == ".tar.gz")
is_encrypted = False # Individual folders are not encrypted in this logic
backup_type = backup_type_base
backup_type = backup_type_base.capitalize()
if is_compressed:
backup_type += " (Compressed)"
backup_size = "N/A"
comment = ""
info_file_path = os.path.join(pybackup_path, f"{item}.txt")
info_file_path = os.path.join(scan_path, f"{item}.txt")
if os.path.exists(info_file_path):
try:
with open(info_file_path, 'r') as f:
for line in f:
if line.strip().lower().startswith("originalgröße:"):
size_part = line.split(":", 1)[1].strip()
if '(' in size_part:
backup_size = size_part.split('(')[0].strip()
else:
backup_size = size_part
backup_size = line.split(":", 1)[1].strip().split('(')[0].strip()
elif line.strip().lower().startswith("kommentar:"):
comment = line.split(":", 1)[1].strip()
except Exception as e:
self.logger.log(
f"Could not read info file {info_file_path}: {e}")
self.logger.log(f"Could not read info file {info_file_path}: {e}")
system_backups.append({
all_backups.append({
"date": date_str,
"time": time_str,
"type": backup_type,
@@ -678,20 +630,35 @@ set -e
"full_path": full_path,
"comment": comment,
"is_compressed": is_compressed,
"is_encrypted": is_encrypted,
"backup_type_base": backup_type_base
"is_encrypted": is_encrypted_and_mounted,
"backup_type_base": backup_type_base.capitalize(),
"datetime": datetime.datetime.strptime(f"{date_str} {time_str}", '%d-%m-%Y %H:%M:%S')
})
try:
# Sort chronologically, oldest first
system_backups.sort(key=lambda x: datetime.datetime.strptime(
f"{x['date']} {x['time']}", '%d-%m-%Y %H:%M:%S'), reverse=False)
except ValueError:
self.logger.log(
"Could not sort backups by date and time due to format mismatch.")
system_backups.sort(key=lambda x: x['folder_name'], reverse=False)
# Sort all backups chronologically to make grouping easier
all_backups.sort(key=lambda x: x['datetime'])
return system_backups
# Group backups: each group starts with a Full backup
grouped_backups = []
current_group = []
for backup in all_backups:
if backup['backup_type_base'] == 'Full':
if current_group:
grouped_backups.append(current_group)
current_group = [backup]
else: # Incremental
if current_group: # Add to the current group if it exists
current_group.append(backup)
if current_group:
grouped_backups.append(current_group)
# Sort groups by the datetime of their first (Full) backup, descending
grouped_backups.sort(key=lambda g: g[0]['datetime'], reverse=True)
# Flatten the list of groups into the final sorted list
final_sorted_list = [item for group in grouped_backups for item in group]
return final_sorted_list
def list_user_backups(self, base_backup_path: str) -> List[Dict[str, str]]:
"""Lists all user backups found in the base backup path."""

View File

@@ -25,12 +25,20 @@ class EncryptionManager:
try:
return keyring.get_password(self.service_id, username)
except keyring.errors.InitError as e:
logger.log(f"Could not initialize keyring. Keyring is not available on this system or is not configured correctly. Error: {e}")
self.logger.log(f"Could not initialize keyring. Keyring is not available on this system or is not configured correctly. Error: {e}")
return None
except Exception as e:
logger.log(f"Could not get password from keyring: {e}")
self.logger.log(f"Could not get password from keyring: {e}")
return None
def is_key_in_keyring(self, username: str) -> bool:
"""Checks if a password for the given username exists in the keyring."""
try:
return self.get_password_from_keyring(username) is not None
except Exception as e:
self.logger.log(f"Could not check password in keyring: {e}")
return False
def set_password_in_keyring(self, username: str, password: str) -> bool:
try:
keyring.set_password(self.service_id, username, password)
@@ -69,6 +77,43 @@ class EncryptionManager:
return password
def unlock_container(self, base_path: str, password: str) -> Optional[str]:
"""Unlocks and mounts an existing LUKS container."""
self.logger.log(f"Attempting to unlock encrypted container in {base_path}")
container_file = "pybackup_encrypted.luks"
container_path = os.path.join(base_path, container_file)
if not os.path.exists(container_path):
self.logger.log(f"Encrypted container not found at {container_path}")
return None
mapper_name = f"pybackup_{os.path.basename(base_path.rstrip('/'))}"
mount_point = f"/mnt/{mapper_name}"
if os.path.ismount(mount_point):
self.logger.log(f"Container already mounted at {mount_point}")
return mount_point
script = f"""
mkdir -p {mount_point}
echo -n '{password}' | cryptsetup luksOpen {container_path} {mapper_name} -
mount /dev/mapper/{mapper_name} {mount_point}
"""
if not self._execute_as_root(script):
self.logger.log("Failed to unlock existing encrypted container. Check password or permissions.")
# Clean up failed mount attempt
self.cleanup_encrypted_backup(mapper_name, mount_point)
return None
self.logger.log(f"Encrypted container unlocked and mounted at {mount_point}")
return mount_point
def lock_container(self, base_path: str):
"""Unmounts and closes the LUKS container for a given base path."""
mapper_name = f"pybackup_{os.path.basename(base_path.rstrip('/'))}"
mount_point = f"/mnt/{mapper_name}"
self.cleanup_encrypted_backup(mapper_name, mount_point)
def setup_encrypted_backup(self, queue, base_path: str, size_gb: int, password: str) -> Optional[str]:
"""Sets up a persistent LUKS encrypted container for the backup destination."""
self.logger.log(f"Setting up encrypted container at {base_path}")
@@ -80,7 +125,7 @@ class EncryptionManager:
container_file = "pybackup_encrypted.luks"
container_path = os.path.join(base_path, container_file)
mapper_name = f"pybackup_{os.path.basename(base_path)}"
mapper_name = f"pybackup_{os.path.basename(base_path.rstrip('/'))}"
mount_point = f"/mnt/{mapper_name}"
if not password:
@@ -95,17 +140,7 @@ class EncryptionManager:
if os.path.exists(container_path):
self.logger.log(f"Encrypted container {container_path} already exists. Attempting to unlock.")
script = f"""
mkdir -p {mount_point}
echo -n '{password}' | cryptsetup luksOpen {container_path} {mapper_name} -
mount /dev/mapper/{mapper_name} {mount_point}
"""
if not self._execute_as_root(script):
self.logger.log("Failed to unlock existing encrypted container. Check password or permissions.")
queue.put(('error', "Failed to unlock existing encrypted container."))
# Clean up failed mount attempt
self.cleanup_encrypted_backup(mapper_name, mount_point)
return None
return self.unlock_container(base_path, password)
else:
self.logger.log(f"Creating new encrypted container: {container_path}")
script = f"""
@@ -119,10 +154,11 @@ class EncryptionManager:
if not self._execute_as_root(script):
self.logger.log("Failed to create and setup encrypted container.")
self._cleanup_encrypted_backup(mapper_name, mount_point)
self.cleanup_encrypted_backup(mapper_name, mount_point)
# Also remove the failed container file
if os.path.exists(container_path):
os.remove(container_path) # This should be done with pkexec as well
# This should be done with pkexec as well for safety
self._execute_as_root(f"rm -f {container_path}")
queue.put(('error', "Failed to setup encrypted container."))
return None

View File

@@ -4,6 +4,7 @@ from tkinter import ttk
import os
import datetime
from queue import Queue, Empty
import shutil
from shared_libs.log_window import LogWindow
from shared_libs.logger import app_logger
@@ -146,9 +147,7 @@ class MainApplication(tk.Tk):
self.sidebar_buttons_frame, text=Msg.STR["settings"], command=lambda: self.navigation.toggle_settings_frame(4), style="Sidebar.TButton")
self.settings_button.pack(fill=tk.X, pady=10)
self.header_frame = HeaderFrame(self.content_frame, self.image_manager)
self.header_frame = HeaderFrame(self.content_frame, self.image_manager, self.backup_manager.encryption_manager, self)
self.header_frame.grid(row=0, column=0, sticky="nsew")
self.top_bar = ttk.Frame(self.content_frame)
@@ -223,7 +222,6 @@ class MainApplication(tk.Tk):
self._setup_settings_frame()
self._setup_backup_content_frame()
self._setup_task_bar()
self.source_size_frame = ttk.LabelFrame(
@@ -299,15 +297,13 @@ class MainApplication(tk.Tk):
self.restore_size_frame_after.grid_remove()
self._load_state_and_initialize()
self.update_backup_options_from_config() # Add this call
self.update_backup_options_from_config()
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def _load_state_and_initialize(self):
self.log_window.clear_log()
"""Loads saved state from config and initializes the UI."""
last_mode = self.config_manager.get_setting("last_mode", "backup")
# Pre-load data from config before initializing the UI
backup_source_path = self.config_manager.get_setting(
"backup_source_path")
if backup_source_path and os.path.isdir(backup_source_path):
@@ -317,9 +313,8 @@ class MainApplication(tk.Tk):
if folder_name:
icon_name = self.buttons_map[folder_name]['icon']
else:
# Handle custom folder path
folder_name = os.path.basename(backup_source_path.rstrip('/'))
icon_name = 'folder_extralarge' # A generic folder icon
icon_name = 'folder_extralarge'
self.backup_left_canvas_data.update({
'icon': icon_name,
@@ -330,7 +325,7 @@ class MainApplication(tk.Tk):
backup_dest_path = self.config_manager.get_setting(
"backup_destination_path")
if backup_dest_path and os.path.isdir(backup_dest_path):
self.destination_path = backup_dest_path # Still needed for some logic
self.destination_path = backup_dest_path
total, used, free = shutil.disk_usage(backup_dest_path)
self.backup_right_canvas_data.update({
'folder': os.path.basename(backup_dest_path.rstrip('/')),
@@ -340,7 +335,18 @@ class MainApplication(tk.Tk):
self.destination_total_bytes = total
self.destination_used_bytes = used
if hasattr(self, 'header_frame'):
self.header_frame.refresh_status()
container_path = os.path.join(backup_dest_path, "pybackup_encrypted.luks")
if os.path.exists(container_path):
username = os.path.basename(backup_dest_path.rstrip('/'))
password = self.backup_manager.encryption_manager.get_password_from_keyring(username)
if password:
self.backup_manager.encryption_manager.unlock_container(backup_dest_path, password)
app_logger.log("Automatically unlocked encrypted container.")
if hasattr(self, 'header_frame'):
self.header_frame.refresh_status()
restore_src_path = self.config_manager.get_setting(
"restore_source_path")
@@ -353,7 +359,6 @@ class MainApplication(tk.Tk):
restore_dest_path = self.config_manager.get_setting(
"restore_destination_path")
if restore_dest_path and os.path.isdir(restore_dest_path):
# Find the corresponding button_text for the path
folder_name = ""
for name, path_obj in AppConfig.FOLDER_PATHS.items():
if str(path_obj) == restore_dest_path:
@@ -366,18 +371,14 @@ class MainApplication(tk.Tk):
'path_display': restore_dest_path,
})
# Initialize UI for the last active mode
self.navigation.initialize_ui_for_mode(last_mode)
# Trigger calculations if needed
if last_mode == 'backup':
self.after(100, self.actions.on_sidebar_button_click,
self.backup_left_canvas_data.get('folder', 'Computer'))
elif last_mode == 'restore':
# Trigger calculation for the right canvas (source) if a path is set
if restore_src_path:
self.drawing.calculate_restore_folder_size()
# Trigger calculation for the left canvas (destination) based on last selection
restore_dest_folder = self.restore_left_canvas_data.get(
'folder', 'Computer')
self.after(100, self.actions.on_sidebar_button_click,
@@ -406,14 +407,11 @@ class MainApplication(tk.Tk):
def _setup_backup_content_frame(self):
self.backup_content_frame = BackupContentFrame(
self.content_frame, self.backup_manager, self.actions, padding=10)
self.content_frame, self.backup_manager, self.actions, self, padding=10)
self.backup_content_frame.grid(row=2, column=0, sticky="nsew")
self.backup_content_frame.grid_remove()
def _setup_task_bar(self):
# Define all boolean vars at the top to ensure they exist before use.
self.vollbackup_var = tk.BooleanVar()
self.inkrementell_var = tk.BooleanVar()
self.genaue_berechnung_var = tk.BooleanVar()
@@ -429,7 +427,6 @@ class MainApplication(tk.Tk):
self.info_checkbox_frame, text=Msg.STR["info_text_placeholder"])
self.info_label.pack(anchor=tk.W, fill=tk.X, pady=5)
# Frame for time info
self.time_info_frame = ttk.Frame(self.info_checkbox_frame)
self.time_info_frame.pack(anchor=tk.W, fill=tk.X, pady=5)
@@ -445,7 +442,6 @@ class MainApplication(tk.Tk):
self.time_info_frame, text="Ende: --:--:--")
self.end_time_label.pack(side=tk.LEFT, padx=5)
# --- Accurate Size Calculation Frame (on the right) ---
accurate_size_frame = ttk.Frame(self.time_info_frame)
accurate_size_frame.pack(side=tk.LEFT, padx=20)
@@ -513,13 +509,12 @@ class MainApplication(tk.Tk):
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)
def on_closing(self):
"""Handles window closing events and saves the app state."""
if self.destination_path:
self.backup_manager.encryption_manager.lock_container(self.destination_path)
self.config_manager.set_setting("last_mode", self.mode)
# Save paths from the data dictionaries
if self.backup_left_canvas_data.get('path_display'):
self.config_manager.set_setting(
"backup_source_path", self.backup_left_canvas_data['path_display'])
@@ -544,7 +539,6 @@ class MainApplication(tk.Tk):
else:
self.config_manager.set_setting("restore_source_path", None)
# Stop any ongoing animations before destroying the application
if self.left_canvas_animation:
self.left_canvas_animation.stop()
self.left_canvas_animation.destroy()
@@ -562,21 +556,15 @@ class MainApplication(tk.Tk):
self.destroy()
def _process_queue(self):
"""
Processes all messages from background threads to update the UI safely.
This is the single, consolidated queue processing loop for the entire application.
It processes messages in batches to avoid freezing the UI.
"""
try:
for _ in range(100): # Process up to 100 messages at a time
for _ in range(100):
message = self.queue.get_nowait()
# --- Size Calculation Message Handling (from data_processing) ---
if isinstance(message, tuple) and len(message) in [3, 5]:
calc_type, status = None, None
if len(message) == 5:
button_text, folder_size, mode_when_started, calc_type, status = message
else: # len == 3
else:
button_text, folder_size, mode_when_started = message
if mode_when_started != self.mode:
@@ -641,7 +629,6 @@ class MainApplication(tk.Tk):
text=Msg.STR["accurate_size_failed"], foreground="#D32F2F")
self.current_file_label.config(text="")
# --- Backup/Deletion Message Handling (from main_app) ---
elif isinstance(message, tuple) and len(message) == 2:
message_type, value = message
@@ -707,15 +694,13 @@ class MainApplication(tk.Tk):
self.actions._set_ui_state(True)
self.backup_content_frame.system_backups_frame._load_backup_content()
elif message_type == 'completion_accurate':
# This is now handled by the len=5 case above
pass
else:
app_logger.log(f"Unknown message in queue: {message}")
except Empty:
pass # The queue is empty, do nothing.
pass
finally:
# Always schedule the next check.
self.after(100, self._process_queue)
def _update_duration(self):
@@ -732,11 +717,6 @@ class MainApplication(tk.Tk):
self.on_closing()
def update_backup_options_from_config(self):
"""
Reads the 'force' settings from the config and updates the main UI checkboxes.
A 'force' setting overrides the user's selection and disables the control.
"""
# Full/Incremental Logic
force_full = self.config_manager.get_setting(
"force_full_backup", False)
force_incremental = self.config_manager.get_setting(
@@ -753,14 +733,12 @@ class MainApplication(tk.Tk):
self.full_backup_cb.config(state="disabled")
self.incremental_cb.config(state="disabled")
# Compression Logic
force_compression = self.config_manager.get_setting(
"force_compression", False)
if force_compression:
self.compressed_var.set(True)
self.compressed_cb.config(state="disabled")
# Encryption Logic
force_encryption = self.config_manager.get_setting(
"force_encryption", False)
if force_encryption:
@@ -773,7 +751,6 @@ class MainApplication(tk.Tk):
if __name__ == "__main__":
import argparse
import sys
import shutil
parser = argparse.ArgumentParser(description="Py-Backup Application.")
parser.add_argument(

View File

@@ -299,6 +299,8 @@ class Msg:
"cat_documents": _("Documents"),
"cat_music": _("Music"),
"cat_videos": _("Videos"),
"show_encrypted_backups": _("Show Encrypted Backups"),
"show_normal_backups": _("Show Normal Backups"),
# Browser View
"backup_location": _("Backup Location"),
@@ -315,6 +317,8 @@ class Msg:
"err_no_dest_folder": _("Please select a destination folder."),
"err_no_source_folder": _("Please select at least one source folder."),
"err_no_backup_selected": _("Please select a backup from the list."),
"err_unlock_failed": _("Failed to unlock the container. Please check the password and try again."),
"err_encrypted_not_mounted": _("Encrypted container is not unlocked. Please unlock it first from the header bar."),
"confirm_user_restore_title": _("Confirm User Data Restore"),
"confirm_user_restore_msg": _("Do you really want to restore the backup of '{backup_name}' to its original location? Any newer files may be overwritten."),
"confirm_delete_title": _("Confirm Deletion"),
@@ -339,6 +343,7 @@ class Msg:
"projected_usage_label": _("Projected usage after backup"),
"header_title": _("Lx Tools Py-Backup"),
"header_subtitle": _("Simple GUI for rsync"),
"encrypted_backup_content": _("Encrypted Backups"),
"compressed": _("Compressed"),
"encrypted": _("Encrypted"),
"bypass_security": _("Bypass security"),

View File

@@ -345,6 +345,7 @@ class Actions:
})
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()

View File

@@ -1,27 +1,30 @@
import tkinter as tk
from tkinter import ttk
import os
from pbp_app_config import Msg
from pyimage_ui.system_backup_content_frame import SystemBackupContentFrame
from pyimage_ui.user_backup_content_frame import UserBackupContentFrame
from shared_libs.animated_icon import AnimatedIcon
from shared_libs.logger import app_logger
from shared_libs.message import MessageDialog
class BackupContentFrame(ttk.Frame):
def __init__(self, master, backup_manager, actions, **kwargs):
def __init__(self, master, backup_manager, actions, app, **kwargs):
super().__init__(master, **kwargs)
app_logger.log("BackupContentFrame: __init__ called")
self.backup_manager = backup_manager
self.actions = actions
self.master = master
self.app = app
self.backup_path = None
self.base_backup_path = None
self.current_view_index = 0
self.viewing_encrypted = False
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
header_frame = ttk.Frame(self)
header_frame.grid(row=0, column=0, sticky=tk.NSEW)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
top_nav_frame = ttk.Frame(header_frame)
top_nav_frame.pack(side=tk.LEFT)
@@ -50,32 +53,82 @@ class BackupContentFrame(ttk.Frame):
ttk.Separator(top_nav_frame, orient=tk.VERTICAL).pack(
side=tk.LEFT, fill=tk.Y, padx=2)
# Deletion Status UI
self.deletion_status_frame = ttk.Frame(header_frame)
self.deletion_status_frame.pack(side=tk.LEFT, padx=15)
content_container = ttk.Frame(self)
content_container.grid(row=1, column=0, sticky="nsew")
content_container.grid_rowconfigure(0, weight=1)
content_container.grid_columnconfigure(0, weight=1)
bg_color = self.winfo_toplevel().style.lookup('TFrame', 'background')
self.deletion_animated_icon = AnimatedIcon(
self.deletion_status_frame, width=20, height=20, use_pillow=True, bg=bg_color, animation_type="counter_arc")
self.deletion_animated_icon.pack(side=tk.LEFT, padx=5)
self.deletion_animated_icon.stop("DISABLE")
self.system_backups_frame = SystemBackupContentFrame(content_container, backup_manager, actions, parent_view=self)
self.user_backups_frame = UserBackupContentFrame(content_container, backup_manager, actions, parent_view=self)
self.system_backups_frame.grid(row=0, column=0, sticky=tk.NSEW)
self.user_backups_frame.grid(row=0, column=0, sticky=tk.NSEW)
self.deletion_status_label = ttk.Label(
self.deletion_status_frame, text="", font=("Ubuntu", 10, "bold"))
self.deletion_status_label.pack(side=tk.LEFT, padx=5)
action_button_frame = ttk.Frame(self, padding=10)
action_button_frame.grid(row=2, column=0, sticky="ew")
# --- Content Frames ---
self.system_backups_frame = SystemBackupContentFrame(
self, backup_manager, actions)
self.user_backups_frame = UserBackupContentFrame(self, backup_manager, actions)
self.toggle_encrypted_button = ttk.Button(action_button_frame, text=Msg.STR["show_encrypted_backups"], command=self._toggle_encrypted_view)
self.toggle_encrypted_button.pack(side=tk.LEFT, padx=5)
self.system_backups_frame.grid(row=1, column=0, sticky=tk.NSEW)
self.user_backups_frame.grid(row=1, column=0, sticky=tk.NSEW)
self.restore_button = ttk.Button(action_button_frame, text=Msg.STR["restore"], command=self._restore_selected, state="disabled")
self.restore_button.pack(side=tk.LEFT, padx=5)
self._switch_view(self.current_view_index)
self.delete_button = ttk.Button(action_button_frame, text=Msg.STR["delete"], command=self._delete_selected, state="disabled")
self.delete_button.pack(side=tk.LEFT, padx=5)
self.edit_comment_button = ttk.Button(action_button_frame, text=Msg.STR["comment"], command=self._edit_comment, state="disabled")
self.edit_comment_button.pack(side=tk.LEFT, padx=5)
self._switch_view(0)
def update_button_state(self, is_selected):
self.restore_button.config(state="normal" if is_selected else "disabled")
self.delete_button.config(state="normal" if is_selected else "disabled")
self.edit_comment_button.config(state="normal" if is_selected else "disabled")
def _get_active_subframe(self):
return self.system_backups_frame if self.current_view_index == 0 else self.user_backups_frame
def _restore_selected(self):
self._get_active_subframe()._restore_selected()
def _delete_selected(self):
self._get_active_subframe()._delete_selected()
def _edit_comment(self):
self._get_active_subframe()._edit_comment()
def _toggle_encrypted_view(self):
if not self.app.destination_path:
MessageDialog(master=self.app, message_type="info", title=Msg.STR["info_menu"], text=Msg.STR["err_no_dest_folder"])
return
pybackup_dir = os.path.join(self.app.destination_path, "pybackup")
if not self.viewing_encrypted:
username = os.path.basename(self.app.destination_path.rstrip('/'))
password = self.app.backup_manager.encryption_manager.get_password(username, confirm=False)
if not password:
return
mount_point = self.app.backup_manager.encryption_manager.unlock_container(pybackup_dir, password)
if mount_point:
self.viewing_encrypted = True
self.toggle_encrypted_button.config(text=Msg.STR["show_normal_backups"])
self.show(mount_point)
self.app.header_frame.refresh_status()
else:
MessageDialog(master=self.app, message_type="error", title=Msg.STR["error"], text=Msg.STR["err_unlock_failed"])
else:
self.app.backup_manager.encryption_manager.lock_container(pybackup_dir)
self.viewing_encrypted = False
self.toggle_encrypted_button.config(text=Msg.STR["show_encrypted_backups"])
self.show(self.app.destination_path)
self.app.header_frame.refresh_status()
def _switch_view(self, index):
self.current_view_index = index
config_key = "last_encrypted_backup_content_view" if self.viewing_encrypted else "last_backup_content_view"
self.app.config_manager.set_setting(config_key, index)
self.update_nav_buttons(index)
if index == 0:
@@ -84,6 +137,7 @@ class BackupContentFrame(ttk.Frame):
else:
self.user_backups_frame.grid()
self.system_backups_frame.grid_remove()
self.update_button_state(False)
def update_nav_buttons(self, active_index):
for i, button in enumerate(self.nav_buttons):
@@ -96,26 +150,26 @@ class BackupContentFrame(ttk.Frame):
self.nav_progress_bars[i].pack_forget()
def show(self, backup_path):
app_logger.log(f"BackupContentFrame: show called with path {backup_path}")
self.grid(row=2, column=0, sticky="nsew")
if backup_path and self.backup_path != backup_path:
self.backup_path = backup_path
self.system_backups_frame.show(backup_path)
self.user_backups_frame.show(backup_path)
# Ensure the correct view is shown upon revealing the frame
self._switch_view(self.current_view_index)
self.base_backup_path = backup_path
if self.viewing_encrypted:
actual_backup_path = backup_path
else:
actual_backup_path = os.path.join(backup_path, "pybackup")
def hide(self):
self.grid_remove()
if not os.path.isdir(actual_backup_path):
app_logger.log(f"Backup path {actual_backup_path} does not exist or is not a directory.")
# Clear views if path is invalid
self.system_backups_frame.show(None)
self.user_backups_frame.show(None)
return
def show_deletion_status(self, text: str):
app_logger.log(f"Showing deletion status: {text}")
self.deletion_status_label.config(text=text)
self.deletion_animated_icon.start()
self.deletion_status_frame.pack(side=tk.LEFT, padx=15)
def hide_deletion_status(self):
app_logger.log("Hiding deletion status.")
self.deletion_animated_icon.stop("DISABLE")
self.deletion_status_frame.pack_forget()
self.system_backups_frame.show(actual_backup_path)
self.user_backups_frame.show(actual_backup_path)
config_key = "last_encrypted_backup_content_view" if self.viewing_encrypted else "last_backup_content_view"
last_view = self.app.config_manager.get_setting(config_key, 0)
self._switch_view(last_view)

View File

@@ -1,14 +1,16 @@
import tkinter as tk
import os
from pbp_app_config import Msg
from shared_libs.common_tools import IconManager
class HeaderFrame(tk.Frame):
def __init__(self, container, image_manager, **kwargs):
def __init__(self, container, image_manager, encryption_manager, app, **kwargs):
super().__init__(container, bg="#455A64", **kwargs)
self.image_manager = image_manager
self.encryption_manager = encryption_manager
self.app = app
# Configure grid weights for internal layout
self.columnconfigure(1, weight=1) # Make the middle column expand
@@ -48,18 +50,48 @@ class HeaderFrame(tk.Frame):
subtitle_label.grid(row=1, column=1, sticky="w",
padx=(5, 20), pady=(0, 10))
# Right side: Placeholder for future info or buttons
# Right side: Keyring status
right_frame = tk.Frame(self, bg="#455A64")
right_frame.grid(row=0, column=2, rowspan=2, sticky="nsew")
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(0, weight=1)
# Example of content for the right side (can be removed or replaced)
# info_label = tk.Label(
# right_frame,
# text="Some Info Here",
# font=("Helvetica", 10),
# fg="#ecf0f1",
# bg="#455A64",
# )
# info_label.grid(row=0, column=0, sticky="ne", padx=(10, 10), pady=(10, 0))
self.keyring_status_label = tk.Label(
right_frame,
text="",
font=("Helvetica", 10, "bold"),
bg="#455A64",
)
self.keyring_status_label.grid(row=0, column=0, sticky="ne", padx=(10, 10), pady=(10, 0))
self.refresh_status()
def refresh_status(self):
"""Checks the keyring status based on the current destination and updates the label."""
dest_path = self.app.destination_path
if not dest_path:
self.keyring_status_label.config(
text="Keyring: N/A",
fg="#A9A9A9" # DarkGray
)
return
username = os.path.basename(dest_path.rstrip('/'))
mapper_name = f"pybackup_{username}"
mount_point = f"/mnt/{mapper_name}"
if os.path.ismount(mount_point):
self.keyring_status_label.config(
text="Keyring: In Use",
fg="#2E8B57" # SeaGreen
)
elif self.encryption_manager.is_key_in_keyring(username):
self.keyring_status_label.config(
text="Keyring: Available",
fg="#FFD700" # Gold
)
else:
self.keyring_status_label.config(
text="Keyring: Not in Use",
fg="#A9A9A9" # DarkGray
)

View File

@@ -141,7 +141,7 @@ class Navigation:
self.app.log_frame.grid_remove()
self.app.scheduler_frame.hide()
self.app.settings_frame.hide()
self.app.backup_content_frame.hide()
self.app.backup_content_frame.grid_remove()
# Show the main content frames
@@ -186,7 +186,7 @@ class Navigation:
self.app.log_frame.grid_remove()
self.app.scheduler_frame.hide()
self.app.settings_frame.hide()
self.app.backup_content_frame.hide()
self.app.backup_content_frame.grid_remove()
self.app.canvas_frame.grid()
self.app.source_size_frame.grid()
@@ -224,7 +224,7 @@ class Navigation:
self.app.canvas_frame.grid_remove()
self.app.scheduler_frame.hide()
self.app.settings_frame.hide()
self.app.backup_content_frame.hide()
self.app.backup_content_frame.grid_remove()
self.app.source_size_frame.grid_remove()
self.app.target_size_frame.grid_remove()
self.app.restore_size_frame_before.grid_remove()
@@ -241,7 +241,7 @@ class Navigation:
self.app.canvas_frame.grid_remove()
self.app.log_frame.grid_remove()
self.app.settings_frame.hide()
self.app.backup_content_frame.hide()
self.app.backup_content_frame.grid_remove()
self.app.source_size_frame.grid_remove()
self.app.target_size_frame.grid_remove()
@@ -258,7 +258,7 @@ class Navigation:
self.app.canvas_frame.grid_remove()
self.app.log_frame.grid_remove()
self.app.backup_content_frame.hide()
self.app.backup_content_frame.grid_remove()
self.app.scheduler_frame.hide()
self.app.source_size_frame.grid_remove()

View File

@@ -5,17 +5,15 @@ import os
from pbp_app_config import Msg
from pyimage_ui.comment_editor_dialog import CommentEditorDialog
class SystemBackupContentFrame(ttk.Frame):
def __init__(self, master, backup_manager, actions, **kwargs):
def __init__(self, master, backup_manager, actions, parent_view, **kwargs):
super().__init__(master, **kwargs)
self.backup_manager = backup_manager
self.actions = actions
self.parent_view = parent_view
self.system_backups_list = []
self.backup_path = None
# --- Color Tags ---
self.tag_colors = [
("full_blue", "#0078D7", "inc_blue", "#50E6FF"),
("full_orange", "#E8740C", "inc_orange", "#FFB366"),
@@ -23,24 +21,13 @@ class SystemBackupContentFrame(ttk.Frame):
("full_purple", "#8B107C", "inc_purple", "#D46EE5"),
]
# --- Backup Content List View ---
self.content_frame = ttk.LabelFrame(
self, text=Msg.STR["backup_content"], padding=10)
self.content_frame.pack(fill=tk.BOTH, expand=True)
columns = ("date", "time", "type", "size", "comment")
self.content_tree = ttk.Treeview(
self.content_frame, columns=columns, show="headings")
self.content_tree.heading(
"date", text=Msg.STR["date"])
self.content_tree.heading(
"time", text=Msg.STR["time"])
self.content_tree.heading(
"type", text=Msg.STR["type"])
self.content_tree.heading(
"size", text=Msg.STR["size"])
self.content_tree.heading(
"comment", text=Msg.STR["comment"])
self.content_tree = ttk.Treeview(self, columns=columns, show="headings")
self.content_tree.heading("date", text=Msg.STR["date"])
self.content_tree.heading("time", text=Msg.STR["time"])
self.content_tree.heading("type", text=Msg.STR["type"])
self.content_tree.heading("size", text=Msg.STR["size"])
self.content_tree.heading("comment", text=Msg.STR["comment"])
self.content_tree.column("date", width=100, anchor="w")
self.content_tree.column("time", width=80, anchor="center")
@@ -49,31 +36,11 @@ class SystemBackupContentFrame(ttk.Frame):
self.content_tree.column("comment", width=300, anchor="w")
self.content_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.content_tree.bind("<<TreeviewSelect>>", self._on_item_select)
list_button_frame = ttk.Frame(self.content_frame)
list_button_frame.pack(pady=10)
self.restore_button = ttk.Button(list_button_frame, text=Msg.STR["restore"],
command=self._restore_selected, state="disabled")
self.restore_button.pack(side=tk.LEFT, padx=5)
self.delete_button = ttk.Button(list_button_frame, text=Msg.STR["delete"],
command=self._delete_selected, state="disabled")
self.delete_button.pack(side=tk.LEFT, padx=5)
self.edit_comment_button = ttk.Button(list_button_frame, text="Kommentar bearbeiten",
command=self._edit_comment, state="disabled")
self.edit_comment_button.pack(side=tk.LEFT, padx=5)
def show(self, backup_path):
if backup_path and self.backup_path != backup_path:
self.backup_path = backup_path
self._load_backup_content()
def hide(self):
self.grid_remove()
self.backup_path = backup_path
self._load_backup_content()
def _load_backup_content(self):
for i in self.content_tree.get_children():
@@ -82,23 +49,19 @@ class SystemBackupContentFrame(ttk.Frame):
if not self.backup_path or not os.path.isdir(self.backup_path):
return
system_backups = self.backup_manager.list_system_backups(self.backup_path)
self.system_backups_list = system_backups # Store for later use
self.system_backups_list = self.backup_manager.list_system_backups(self.backup_path)
color_index = 0
for i, backup_info in enumerate(system_backups):
# Determine the color tag for the group
color_index = -1
for i, backup_info in enumerate(self.system_backups_list):
if backup_info.get("backup_type_base") == "Full":
if i > 0: # Not the very first backup
color_index = (color_index + 1) % len(self.tag_colors)
tag_name, color, _, _ = self.tag_colors[color_index]
self.content_tree.tag_configure(tag_name, foreground=color)
current_tag = tag_name
else: # Incremental
_, _, tag_name, color = self.tag_colors[color_index]
self.content_tree.tag_configure(tag_name, foreground=color)
current_tag = tag_name
color_index = (color_index + 1) % len(self.tag_colors)
full_tag, full_color, inc_tag, inc_color = self.tag_colors[color_index]
self.content_tree.tag_configure(full_tag, foreground=full_color)
self.content_tree.tag_configure(inc_tag, foreground=inc_color, font=("Helvetica", 10, "bold"))
current_tag = full_tag
else:
_, _, inc_tag, _ = self.tag_colors[color_index]
current_tag = inc_tag
self.content_tree.insert("", "end", values=(
backup_info.get("date", "N/A"),
@@ -108,79 +71,57 @@ class SystemBackupContentFrame(ttk.Frame):
backup_info.get("comment", ""),
), tags=(current_tag,), iid=backup_info.get("folder_name"))
self._on_item_select(None) # Disable buttons initially
self._on_item_select(None)
def _on_item_select(self, event):
selected_item = self.content_tree.focus()
is_selected = True if selected_item else False
self.restore_button.config(
state="normal" if is_selected else "disabled")
self.delete_button.config(
state="normal" if is_selected else "disabled")
self.edit_comment_button.config(
state="normal" if is_selected else "disabled")
is_selected = True if self.content_tree.focus() else False
self.parent_view.update_button_state(is_selected)
def _edit_comment(self):
selected_item_id = self.content_tree.focus()
if not selected_item_id:
return
# Construct the path to the info file
pybackup_path = os.path.join(self.backup_path, "pybackup")
info_file_path = os.path.join(pybackup_path, f"{selected_item_id}.txt")
info_file_path = os.path.join(self.backup_path, f"{selected_item_id}.txt")
if not os.path.exists(info_file_path):
self.backup_manager.update_comment(info_file_path, "")
CommentEditorDialog(self, info_file_path, self.backup_manager)
self._load_backup_content() # Refresh list to show new comment
self._load_backup_content()
def _restore_selected(self):
selected_item_id = self.content_tree.focus()
if not selected_item_id:
return
selected_backup = None
for backup in self.system_backups_list:
if backup.get("folder_name") == selected_item_id:
selected_backup = backup
break
selected_backup = next((b for b in self.system_backups_list if b.get("folder_name") == selected_item_id), None)
if not selected_backup:
print(f"Error: Could not find backup info for {selected_item_id}")
return
try:
main_app = self.winfo_toplevel()
restore_dest_path = main_app.config_manager.get_setting(
"restore_destination_path", "/")
main_app = self.winfo_toplevel()
restore_dest_path = main_app.config_manager.get_setting("restore_destination_path", "/")
if not restore_dest_path:
print("Error: Restore destination not set.")
return
if not restore_dest_path:
return
self.backup_manager.start_restore(
source_path=selected_backup['full_path'],
dest_path=restore_dest_path,
is_compressed=selected_backup['is_compressed']
)
except AttributeError:
print("Could not access main application instance to get restore path.")
self.backup_manager.start_restore(
source_path=selected_backup['full_path'],
dest_path=restore_dest_path,
is_compressed=selected_backup['is_compressed']
)
def _delete_selected(self):
selected_item_id = self.content_tree.focus()
if not selected_item_id:
return
# Construct the full path to the backup folder
pybackup_path = os.path.join(self.backup_path, "pybackup")
folder_to_delete = os.path.join(pybackup_path, selected_item_id)
folder_to_delete = os.path.join(self.backup_path, selected_item_id)
# Lock UI and show status
self.actions._set_ui_state(False) # Lock UI
self.master.show_deletion_status(
Msg.STR["deleting_backup_in_progress"])
self.actions._set_ui_state(False)
# This needs to be adapted, as the deletion status is now in the parent view
# self.master.show_deletion_status(Msg.STR["deleting_backup_in_progress"])
# Start deletion in background
self.backup_manager.start_delete_system_backup(
folder_to_delete, self.winfo_toplevel().queue)
folder_to_delete, self.winfo_toplevel().queue)

View File

@@ -1,87 +1,28 @@
import tkinter as tk
from tkinter import ttk
import os
import shutil
from pbp_app_config import Msg
from pyimage_ui.comment_editor_dialog import CommentEditorDialog
from shared_libs.message import MessageDialog
class UserBackupContentFrame(ttk.Frame):
def __init__(self, master, backup_manager, actions, **kwargs):
super().__init__(master, **kwargs)
self.backup_manager = backup_manager
self.backup_path = None
self.actions = actions # Store actions object
# --- Backup Content List View ---
self.content_frame = ttk.LabelFrame(
self, text=Msg.STR["backup_content"], padding=10)
self.content_frame.pack(fill=tk.BOTH, expand=True)
columns = ("date", "size", "comment", "folder_name")
self.content_tree = ttk.Treeview(
self.content_frame, columns=columns, show="headings")
self.content_tree.heading(
"date", text=Msg.STR["date"])
self.content_tree.heading(
"size", text=Msg.STR["size"])
self.content_tree.heading(
"comment", text=Msg.STR["comment"])
self.content_tree.heading(
"folder_name", text=Msg.STR["folder"])
self.content_tree.column("date", width=120, anchor="w")
self.content_tree.column("size", width=100, anchor="e")
self.content_tree.column("comment", width=200, anchor="w")
self.content_tree.column("folder_name", width=250, anchor="w")
self.content_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.content_tree.bind("<<TreeviewSelect>>", self._on_item_select)
list_button_frame = ttk.Frame(self.content_frame)
list_button_frame.pack(pady=10)
self.restore_button = ttk.Button(list_button_frame, text=Msg.STR["restore"],
command=self._restore_selected, state="disabled")
self.restore_button.pack(side=tk.LEFT, padx=5)
self.delete_button = ttk.Button(list_button_frame, text=Msg.STR["delete"],
command=self._delete_selected, state="disabled")
self.delete_button.pack(side=tk.LEFT, padx=5)
import tkinter as tk
from tkinter import ttk
import os
from pbp_app_config import Msg
from pyimage_ui.comment_editor_dialog import CommentEditorDialog
class UserBackupContentFrame(ttk.Frame):
def __init__(self, master, backup_manager, actions, **kwargs):
def __init__(self, master, backup_manager, actions, parent_view, **kwargs):
super().__init__(master, **kwargs)
self.backup_manager = backup_manager
self.actions = actions
self.parent_view = parent_view
self.user_backups_list = []
self.backup_path = None
# --- Backup Content List View ---
self.content_frame = ttk.LabelFrame(
self, text=Msg.STR["backup_content"], padding=10)
self.content_frame.pack(fill=tk.BOTH, expand=True)
columns = ("date", "time", "size", "comment", "folder_name")
self.content_tree = ttk.Treeview(
self.content_frame, columns=columns, show="headings")
self.content_tree.heading(
"date", text=Msg.STR["date"])
self.content_tree.heading(
"time", text=Msg.STR["time"])
self.content_tree.heading(
"size", text=Msg.STR["size"])
self.content_tree.heading(
"comment", text=Msg.STR["comment"])
self.content_tree.heading(
"folder_name", text=Msg.STR["folder"])
self.content_tree = ttk.Treeview(self, columns=columns, show="headings")
self.content_tree.heading("date", text=Msg.STR["date"])
self.content_tree.heading("time", text=Msg.STR["time"])
self.content_tree.heading("size", text=Msg.STR["size"])
self.content_tree.heading("comment", text=Msg.STR["comment"])
self.content_tree.heading("folder_name", text=Msg.STR["folder"])
self.content_tree.column("date", width=100, anchor="w")
self.content_tree.column("time", width=80, anchor="center")
@@ -90,31 +31,11 @@ class UserBackupContentFrame(ttk.Frame):
self.content_tree.column("folder_name", width=200, anchor="w")
self.content_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.content_tree.bind("<<TreeviewSelect>>", self._on_item_select)
list_button_frame = ttk.Frame(self.content_frame)
list_button_frame.pack(pady=10)
self.restore_button = ttk.Button(list_button_frame, text=Msg.STR["restore"],
command=self._restore_selected, state="disabled")
self.restore_button.pack(side=tk.LEFT, padx=5)
self.delete_button = ttk.Button(list_button_frame, text=Msg.STR["delete"],
command=self._delete_selected, state="disabled")
self.delete_button.pack(side=tk.LEFT, padx=5)
self.edit_comment_button = ttk.Button(list_button_frame, text="Kommentar bearbeiten",
command=self._edit_comment, state="disabled")
self.edit_comment_button.pack(side=tk.LEFT, padx=5)
def show(self, backup_path):
if backup_path and self.backup_path != backup_path:
self.backup_path = backup_path
self._load_backup_content()
def hide(self):
self.grid_remove()
self.backup_path = backup_path
self._load_backup_content()
def _load_backup_content(self):
for i in self.content_tree.get_children():
@@ -133,17 +54,11 @@ class UserBackupContentFrame(ttk.Frame):
backup_info.get("comment", ""),
backup_info.get("folder_name", "N/A")
), iid=backup_info.get("folder_name"))
self._on_item_select(None) # Disable buttons initially
self._on_item_select(None)
def _on_item_select(self, event):
selected_item = self.content_tree.focus()
is_selected = True if selected_item else False
self.restore_button.config(
state="normal" if is_selected else "disabled")
self.delete_button.config(
state="normal" if is_selected else "disabled")
self.edit_comment_button.config(
state="normal" if is_selected else "disabled")
is_selected = True if self.content_tree.focus() else False
self.parent_view.update_button_state(is_selected)
def _edit_comment(self):
selected_item_id = self.content_tree.focus()
@@ -156,109 +71,37 @@ class UserBackupContentFrame(ttk.Frame):
self.backup_manager.update_comment(info_file_path, "")
CommentEditorDialog(self, info_file_path, self.backup_manager)
self._load_backup_content() # Refresh list to show new comment
self._load_backup_content()
def _restore_selected(self):
# This functionality needs to be implemented based on app requirements
pass
selected_item_id = self.content_tree.focus()
if not selected_item_id:
return
MessageDialog(master=self, message_type="info", title="Info", text="User restore not implemented yet.")
def _delete_selected(self):
# This functionality needs to be implemented based on app requirements
pass
self.edit_comment_button.pack(side=tk.LEFT, padx=5)
def show(self, backup_path):
if backup_path and self.backup_path != backup_path:
self.backup_path = backup_path
self._load_backup_content()
def hide(self):
self.grid_remove()
def _load_backup_content(self):
for i in self.content_tree.get_children():
self.content_tree.delete(i)
if not self.backup_path or not os.path.isdir(self.backup_path):
selected_item_id = self.content_tree.focus()
if not selected_item_id:
return
user_backups = self.backup_manager.list_user_backups(self.backup_path)
folder_to_delete = os.path.join(self.backup_path, selected_item_id)
info_file_to_delete = os.path.join(self.backup_path, f"{selected_item_id}.txt")
for backup_info in user_backups:
self.content_tree.insert("", "end", values=(
backup_info.get("date", "N/A"),
backup_info.get("size", "N/A"),
backup_info.get("comment", ""),
backup_info.get("folder_name", "N/A")
))
self._on_item_select(None)
def _on_item_select(self, event):
selected_item = self.content_tree.focus()
is_selected = True if selected_item else False
self.restore_button.config(
state="normal" if is_selected else "disabled")
self.delete_button.config(
state="normal" if is_selected else "disabled")
self.edit_comment_button.config(
state="normal" if is_selected else "disabled")
def _edit_comment(self):
selected_item = self.content_tree.focus()
if not selected_item:
return
item_values = self.content_tree.item(selected_item)["values"]
folder_name = item_values[3] # Assuming folder_name is the 4th value
# Construct the path to the info file
info_file_path = os.path.join(self.backup_path, f"{folder_name}.txt")
if not os.path.exists(info_file_path):
self.backup_manager.update_comment(info_file_path, "")
CommentEditorDialog(self, info_file_path, self.backup_manager)
self._load_backup_content() # Refresh list to show new comment
def _restore_selected(self):
# Placeholder for restore logic
selected_item = self.content_tree.focus()
if not selected_item:
return
backup_name = self.content_tree.item(selected_item)["values"][0]
print(f"Restoring {backup_name}...")
def _delete_selected(self):
selected_item = self.content_tree.focus()
if not selected_item:
return
item_values = self.content_tree.item(selected_item)["values"]
folder_name = item_values[3] # Assuming folder_name is the 4th value
# Construct the full path to the backup folder
folder_to_delete = os.path.join(self.backup_path, folder_name)
info_file_to_delete = os.path.join(
self.backup_path, f"{folder_name}.txt")
# Ask for confirmation
from shared_libs.message import MessageDialog
dialog = MessageDialog(master=self, message_type="warning",
title=Msg.STR["confirm_delete_title"],
text=Msg.STR["confirm_delete_text"].format(
folder_name=folder_name),
folder_name=selected_item_id),
buttons=["ok_cancel"])
if dialog.get_result() != "ok":
return
try:
import shutil
if os.path.isdir(folder_to_delete):
shutil.rmtree(folder_to_delete)
if os.path.exists(info_file_to_delete):
os.remove(info_file_to_delete)
self._load_backup_content()
except Exception as e:
MessageDialog(master=self, message_type="error",
title=Msg.STR["error"], text=str(e))
self._load_backup_content()