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.
This commit is contained in:
@@ -326,7 +326,7 @@ set -e
|
||||
size_str = f"{display_size:.2f} {power_labels[n]}"
|
||||
else:
|
||||
size_str = "0 B"
|
||||
date_str = datetime.datetime.now().strftime("%d. %B %Y, %H:%M:%S")
|
||||
date_str = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
|
||||
|
||||
info_content = (
|
||||
f"Backup-Datum: {date_str}\n"
|
||||
@@ -590,11 +590,47 @@ set -e
|
||||
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
|
||||
name_regex = re.compile(
|
||||
r"^(\d{1,2}-\w+-\d{4})_(\d{6})_system_(full|incremental)(\.tar\.gz|\.luks)?$", re.IGNORECASE)
|
||||
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):
|
||||
if item.endswith('.txt'):
|
||||
if item.endswith('.txt') or item.endswith('.luks'):
|
||||
continue
|
||||
|
||||
match = name_regex.match(item)
|
||||
@@ -603,16 +639,15 @@ set -e
|
||||
|
||||
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)
|
||||
is_compressed = (extension == ".tar.gz")
|
||||
is_encrypted = (extension == ".luks")
|
||||
is_encrypted = False # Individual folders are not encrypted in this logic
|
||||
|
||||
backup_type = backup_type_base
|
||||
if is_compressed:
|
||||
backup_type += " (Compressed)"
|
||||
elif is_encrypted:
|
||||
backup_type += " (Encrypted)"
|
||||
|
||||
backup_size = "N/A"
|
||||
comment = ""
|
||||
@@ -623,11 +658,11 @@ set -e
|
||||
with open(info_file_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.strip().lower().startswith("originalgröße:"):
|
||||
size_match = re.search(r":\s*(.*?)\s*(" , line)
|
||||
if size_match:
|
||||
backup_size = size_match.group(1).strip()
|
||||
size_part = line.split(":", 1)[1].strip()
|
||||
if '(' in size_part:
|
||||
backup_size = size_part.split('(')[0].strip()
|
||||
else:
|
||||
backup_size = line.split(":")[1].strip()
|
||||
backup_size = size_part
|
||||
elif line.strip().lower().startswith("kommentar:"):
|
||||
comment = line.split(":", 1)[1].strip()
|
||||
except Exception as e:
|
||||
@@ -636,22 +671,25 @@ set -e
|
||||
|
||||
system_backups.append({
|
||||
"date": date_str,
|
||||
"time": time_str,
|
||||
"type": backup_type,
|
||||
"size": backup_size,
|
||||
"folder_name": item,
|
||||
"full_path": full_path,
|
||||
"comment": comment,
|
||||
"is_compressed": is_compressed,
|
||||
"is_encrypted": is_encrypted
|
||||
"is_encrypted": is_encrypted,
|
||||
"backup_type_base": backup_type_base
|
||||
})
|
||||
|
||||
try:
|
||||
# Sort chronologically, oldest first
|
||||
system_backups.sort(key=lambda x: datetime.datetime.strptime(
|
||||
x['date'], '%d-%B-%Y'), reverse=True)
|
||||
f"{x['date']} {x['time']}", '%d-%m-%Y %H:%M:%S'), reverse=False)
|
||||
except ValueError:
|
||||
self.logger.log(
|
||||
"Could not sort backups by date due to format mismatch.")
|
||||
system_backups.sort(key=lambda x: x['folder_name'], reverse=True)
|
||||
"Could not sort backups by date and time due to format mismatch.")
|
||||
system_backups.sort(key=lambda x: x['folder_name'], reverse=False)
|
||||
|
||||
return system_backups
|
||||
|
||||
@@ -671,18 +709,25 @@ set -e
|
||||
if os.path.exists(info_file_path):
|
||||
backup_size = "N/A"
|
||||
backup_date = "N/A"
|
||||
backup_time = "N/A"
|
||||
comment = ""
|
||||
try:
|
||||
with open(info_file_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.strip().lower().startswith("originalgröße:"):
|
||||
size_match = re.search(r":\s*(.*?)\s*(" , line)
|
||||
if size_match:
|
||||
backup_size = size_match.group(1).strip()
|
||||
size_part = line.split(":", 1)[1].strip()
|
||||
if '(' in size_part:
|
||||
backup_size = size_part.split('(')[0].strip()
|
||||
else:
|
||||
backup_size = line.split(":")[1].strip()
|
||||
backup_size = size_part
|
||||
elif line.strip().lower().startswith("backup-datum:"):
|
||||
backup_date = line.split(":", 1)[1].strip()
|
||||
full_date_str = line.split(":", 1)[1].strip()
|
||||
date_parts = full_date_str.split()
|
||||
if len(date_parts) >= 2:
|
||||
backup_date = date_parts[0]
|
||||
backup_time = date_parts[1]
|
||||
else:
|
||||
backup_date = full_date_str
|
||||
elif line.strip().lower().startswith("kommentar:"):
|
||||
comment = line.split(":", 1)[1].strip()
|
||||
except Exception as e:
|
||||
@@ -691,13 +736,15 @@ set -e
|
||||
|
||||
user_backups.append({
|
||||
"date": backup_date,
|
||||
"time": backup_time,
|
||||
"size": backup_size,
|
||||
"folder_name": item,
|
||||
"full_path": full_path,
|
||||
"comment": comment
|
||||
})
|
||||
|
||||
user_backups.sort(key=lambda x: x['folder_name'], reverse=True)
|
||||
# Sort chronologically, oldest first
|
||||
user_backups.sort(key=lambda x: f"{x['date']} {x['time']}", reverse=False)
|
||||
return user_backups
|
||||
|
||||
def get_comment(self, info_file_path: str) -> str:
|
||||
|
||||
@@ -622,9 +622,6 @@ class MainApplication(tk.Tk):
|
||||
|
||||
self.drawing.update_target_projection()
|
||||
|
||||
if self.mode == 'backup' and self.destination_path:
|
||||
self.start_pause_button.config(state="normal")
|
||||
|
||||
if calc_type == 'accurate_incremental':
|
||||
self.source_size_bytes = folder_size
|
||||
self.drawing.update_target_projection()
|
||||
|
||||
@@ -239,6 +239,7 @@ class Msg:
|
||||
"name": _("Name"),
|
||||
"path": _("Path"),
|
||||
"date": _("Date"),
|
||||
"time": _("Time"),
|
||||
"size": _("Size"),
|
||||
"type": _("Type"),
|
||||
"folder": _("Folder"),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Actions:
|
||||
system_backups = self.app.backup_manager.list_system_backups(
|
||||
self.app.destination_path)
|
||||
for backup in system_backups:
|
||||
if backup.get('type') == 'Full':
|
||||
if backup.get('backup_type_base') == 'Full':
|
||||
full_backup_exists = True
|
||||
break
|
||||
|
||||
@@ -619,8 +619,8 @@ class Actions:
|
||||
"Could not set locale to de_DE.UTF-8. Using default.")
|
||||
|
||||
now = datetime.datetime.now()
|
||||
date_str = now.strftime("%d-%B-%Y")
|
||||
time_str = now.strftime("%H%M%S")
|
||||
date_str = now.strftime("%d-%m-%Y")
|
||||
time_str = now.strftime("%H:%M:%S")
|
||||
folder_name = f"{date_str}_{time_str}_system_{mode}"
|
||||
final_dest = os.path.join(base_dest, "pybackup", folder_name)
|
||||
self.app.current_backup_path = final_dest
|
||||
|
||||
@@ -234,54 +234,60 @@ class Drawing:
|
||||
self.app.after(50, self.update_target_projection)
|
||||
return
|
||||
|
||||
projected_total_used = self.app.destination_used_bytes + self.app.source_size_bytes
|
||||
projected_total_percentage = projected_total_used / \
|
||||
self.app.destination_total_bytes
|
||||
# Determine required space, considering compression
|
||||
required_space = self.app.source_size_bytes
|
||||
if self.app.compressed_var.get():
|
||||
required_space *= 2 # Double the space for compression process
|
||||
|
||||
info_font = (AppConfig.UI_CONFIG["font_family"], 12, "bold")
|
||||
|
||||
if projected_total_percentage >= 0.95:
|
||||
self.app.start_pause_button.config(state="disabled")
|
||||
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(
|
||||
), fill="#ff0000", outline="") # Red bar
|
||||
elif projected_total_percentage >= 0.90:
|
||||
self.app.start_pause_button.config(state="normal")
|
||||
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(
|
||||
), fill="#ff8c00", outline="") # Orange bar
|
||||
projected_total_used = self.app.destination_used_bytes + required_space
|
||||
|
||||
if self.app.destination_total_bytes > 0:
|
||||
projected_total_percentage = projected_total_used / self.app.destination_total_bytes
|
||||
else:
|
||||
self.app.start_pause_button.config(state="normal")
|
||||
used_percentage = self.app.destination_used_bytes / \
|
||||
self.app.destination_total_bytes
|
||||
used_width = canvas_width * used_percentage
|
||||
canvas.create_rectangle(
|
||||
0, 0, used_width, canvas.winfo_height(), fill="#0078d7", outline="")
|
||||
|
||||
projected_percentage = self.app.source_size_bytes / \
|
||||
self.app.destination_total_bytes
|
||||
projected_width = canvas_width * projected_percentage
|
||||
canvas.create_rectangle(used_width, 0, used_width + projected_width,
|
||||
canvas.winfo_height(), fill="#ff8c00", outline="")
|
||||
projected_total_percentage = 0
|
||||
|
||||
info_font = (AppConfig.UI_CONFIG["font_family"], 10, "bold")
|
||||
info_messages = []
|
||||
if self.app.source_larger_than_partition:
|
||||
info_messages.append(
|
||||
Msg.STR["warning_source_larger_than_partition"])
|
||||
|
||||
if projected_total_percentage >= 0.95:
|
||||
# First, check for critical space issues
|
||||
if projected_total_used > self.app.destination_total_bytes or projected_total_percentage >= 0.95:
|
||||
self.app.start_pause_button.config(state="disabled")
|
||||
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(), fill="#D32F2F", outline="") # Red bar
|
||||
info_messages.append(Msg.STR["warning_not_enough_space"])
|
||||
|
||||
elif projected_total_percentage >= 0.90:
|
||||
self.app.start_pause_button.config(state="normal")
|
||||
canvas.create_rectangle(0, 0, canvas_width, canvas.winfo_height(), fill="#E8740C", outline="") # Orange bar
|
||||
info_messages.append(Msg.STR["warning_space_over_90_percent"])
|
||||
|
||||
else:
|
||||
# Only enable the button if the source is not larger than the partition itself
|
||||
if not self.app.source_larger_than_partition:
|
||||
self.app.start_pause_button.config(state="normal")
|
||||
else:
|
||||
self.app.start_pause_button.config(state="disabled")
|
||||
|
||||
if not info_messages: # If no warnings, show other messages or default text
|
||||
if self.app.is_first_backup:
|
||||
used_percentage = self.app.destination_used_bytes / self.app.destination_total_bytes
|
||||
used_width = canvas_width * used_percentage
|
||||
canvas.create_rectangle(0, 0, used_width, canvas.winfo_height(), fill="#0078d7", outline="")
|
||||
|
||||
# Draw the projected part only if there is space
|
||||
projected_percentage = self.app.source_size_bytes / self.app.destination_total_bytes
|
||||
projected_width = canvas_width * projected_percentage
|
||||
canvas.create_rectangle(used_width, 0, used_width + projected_width, canvas.winfo_height(), fill="#50E6FF", outline="")
|
||||
|
||||
# Add other informational messages if no critical warnings are present
|
||||
if not info_messages:
|
||||
if self.app.source_larger_than_partition:
|
||||
info_messages.append(Msg.STR["warning_source_larger_than_partition"])
|
||||
elif self.app.is_first_backup:
|
||||
info_messages.append(Msg.STR["ready_for_first_backup"])
|
||||
elif self.app.mode == "backup":
|
||||
info_messages.append(Msg.STR["backup_mode_info"])
|
||||
else:
|
||||
info_messages.append(Msg.STR["restore_mode_info"])
|
||||
|
||||
self.app.info_label.config(
|
||||
text="\n".join(info_messages), font=info_font)
|
||||
self.app.info_label.config(text="\n".join(info_messages), font=info_font)
|
||||
|
||||
self.app.target_size_label.config(
|
||||
text=f"{projected_total_used / (1024**3):.2f} GB / {self.app.destination_total_bytes / (1024**3):.2f} GB")
|
||||
|
||||
@@ -15,30 +15,38 @@ class SystemBackupContentFrame(ttk.Frame):
|
||||
|
||||
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", "type", "size", "comment", "folder_name")
|
||||
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.heading(
|
||||
"folder_name", text=Msg.STR["folder"])
|
||||
|
||||
self.content_tree.column("date", width=120, anchor="w")
|
||||
self.content_tree.column("type", width=80, anchor="center")
|
||||
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=200, anchor="w")
|
||||
self.content_tree.column("folder_name", width=250, anchor="w")
|
||||
self.content_tree.column("comment", width=300, anchor="w")
|
||||
|
||||
self.content_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
@@ -74,18 +82,32 @@ class SystemBackupContentFrame(ttk.Frame):
|
||||
if not self.backup_path or not os.path.isdir(self.backup_path):
|
||||
return
|
||||
|
||||
# Use the new method to get structured system backup data
|
||||
system_backups = self.backup_manager.list_system_backups(
|
||||
self.backup_path)
|
||||
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
|
||||
|
||||
for backup_info in system_backups:
|
||||
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", ""),
|
||||
backup_info.get("folder_name", "N/A")
|
||||
))
|
||||
), tags=(current_tag,), iid=backup_info.get("folder_name"))
|
||||
|
||||
self._on_item_select(None) # Disable buttons initially
|
||||
|
||||
def _on_item_select(self, event):
|
||||
@@ -99,56 +121,42 @@ class SystemBackupContentFrame(ttk.Frame):
|
||||
state="normal" if is_selected else "disabled")
|
||||
|
||||
def _edit_comment(self):
|
||||
selected_item = self.content_tree.focus()
|
||||
if not selected_item:
|
||||
selected_item_id = self.content_tree.focus()
|
||||
if not selected_item_id:
|
||||
return
|
||||
|
||||
item_values = self.content_tree.item(selected_item)["values"]
|
||||
folder_name = item_values[4] # Assuming folder_name is the 5th value
|
||||
|
||||
# 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"{folder_name}.txt")
|
||||
info_file_path = os.path.join(pybackup_path, f"{selected_item_id}.txt")
|
||||
|
||||
# The file should exist, but we can handle cases where it might not.
|
||||
if not os.path.exists(info_file_path):
|
||||
# If for some reason the info file is missing, we can create an empty one.
|
||||
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 = self.content_tree.focus()
|
||||
if not selected_item:
|
||||
selected_item_id = self.content_tree.focus()
|
||||
if not selected_item_id:
|
||||
return
|
||||
|
||||
item_values = self.content_tree.item(selected_item)["values"]
|
||||
folder_name = item_values[4] # 5th column is folder_name
|
||||
|
||||
selected_backup = None
|
||||
for backup in self.system_backups_list:
|
||||
if backup.get("folder_name") == folder_name:
|
||||
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 {folder_name}")
|
||||
print(f"Error: Could not find backup info for {selected_item_id}")
|
||||
return
|
||||
|
||||
# We need to get the restore destination from the main app
|
||||
# This is a bit tricky as this frame is isolated.
|
||||
# We assume the main app has a way to provide this.
|
||||
# Let's get it from the config manager, which should be accessible via the backup_manager's app instance.
|
||||
try:
|
||||
# Accessing the app instance through the master hierarchy
|
||||
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.")
|
||||
# Optionally, show a message box to the user
|
||||
return
|
||||
|
||||
self.backup_manager.start_restore(
|
||||
@@ -160,16 +168,13 @@ class SystemBackupContentFrame(ttk.Frame):
|
||||
print("Could not access main application instance to get restore path.")
|
||||
|
||||
def _delete_selected(self):
|
||||
selected_item = self.content_tree.focus()
|
||||
if not selected_item:
|
||||
selected_item_id = self.content_tree.focus()
|
||||
if not selected_item_id:
|
||||
return
|
||||
|
||||
item_values = self.content_tree.item(selected_item)["values"]
|
||||
folder_name = item_values[4] # Assuming folder_name is the 5th value
|
||||
|
||||
# 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, folder_name)
|
||||
folder_to_delete = os.path.join(pybackup_path, selected_item_id)
|
||||
|
||||
# Lock UI and show status
|
||||
self.actions._set_ui_state(False) # Lock UI
|
||||
|
||||
@@ -47,6 +47,63 @@ class UserBackupContentFrame(ttk.Frame):
|
||||
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):
|
||||
super().__init__(master, **kwargs)
|
||||
self.backup_manager = backup_manager
|
||||
self.actions = actions
|
||||
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.column("date", width=100, anchor="w")
|
||||
self.content_tree.column("time", width=80, anchor="center")
|
||||
self.content_tree.column("size", width=100, anchor="e")
|
||||
self.content_tree.column("comment", width=250, anchor="w")
|
||||
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)
|
||||
@@ -59,6 +116,65 @@ class UserBackupContentFrame(ttk.Frame):
|
||||
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
|
||||
|
||||
self.user_backups_list = self.backup_manager.list_user_backups(self.backup_path)
|
||||
|
||||
for backup_info in self.user_backups_list:
|
||||
self.content_tree.insert("", "end", values=(
|
||||
backup_info.get("date", "N/A"),
|
||||
backup_info.get("time", "N/A"),
|
||||
backup_info.get("size", "N/A"),
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def _restore_selected(self):
|
||||
# This functionality needs to be implemented based on app requirements
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user