Files
Py-Backup/pyimage_ui/system_backup_content_frame.py
Désiré Werner Menrath 0b9c58410f feat: Rework backup list and space calculation
- Improves the backup list display with chronological sorting, colored grouping for full/incremental backups, and dedicated time column.
- Changes backup folder naming to a consistent dd-mm-yyyy_HH:MM:SS format.
- Fixes bug where backup size was not displayed.
- Adds detection for encrypted backup containers, showing them correctly in the list.
- Hardens destination space check:
  - Considers extra space needed for compressed backups.
  - Disables start button if projected usage is > 95% or exceeds total disk space.
2025-09-04 14:20:57 +02:00

187 lines
7.3 KiB
Python

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 SystemBackupContentFrame(ttk.Frame):
def __init__(self, master, backup_manager, actions, **kwargs):
super().__init__(master, **kwargs)
self.backup_manager = backup_manager
self.actions = actions
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"),
("full_green", "#107C10", "inc_green", "#50E680"),
("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.column("date", width=100, anchor="w")
self.content_tree.column("time", width=80, anchor="center")
self.content_tree.column("type", width=120, anchor="center")
self.content_tree.column("size", width=100, anchor="e")
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()
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):
return
system_backups = self.backup_manager.list_system_backups(self.backup_path)
self.system_backups_list = system_backups # Store for later use
color_index = 0
for i, backup_info in enumerate(system_backups):
# Determine the color tag for the group
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
self.content_tree.insert("", "end", values=(
backup_info.get("date", "N/A"),
backup_info.get("time", "N/A"),
backup_info.get("type", "N/A"),
backup_info.get("size", "N/A"),
backup_info.get("comment", ""),
), tags=(current_tag,), iid=backup_info.get("folder_name"))
self._on_item_select(None) # Disable buttons initially
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_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")
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):
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
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", "/")
if not restore_dest_path:
print("Error: Restore destination not set.")
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.")
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)
# Lock UI and show status
self.actions._set_ui_state(False) # Lock UI
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)