add new feature to edit comments on backup fix on correct backupzize on info-txt and dreate info text in parent backup dir

This commit is contained in:
2025-08-31 02:33:59 +02:00
parent 9e88ac8bb5
commit f65e7feb67
7 changed files with 219 additions and 17 deletions

View File

@@ -390,6 +390,7 @@ class BackupManager:
if not os.path.isdir(pybackup_path):
return system_backups
# Regex to parse folder names like '6-März-2024_system_full'
name_regex = re.compile(
r"^(\d{1,2}-\w+-\d{4})_system_(full|incremental)$", re.IGNORECASE)
@@ -405,6 +406,7 @@ class BackupManager:
date_str = match.group(1)
backup_type = match.group(2).capitalize()
backup_size = "N/A"
comment = ""
# NEW: Look for info file in the parent directory, named after the backup folder
info_file_path = os.path.join(pybackup_path, f"{item}.txt")
@@ -413,12 +415,14 @@ class BackupManager:
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)
# Extract size, e.g., "Originalgröße: 13.45 GB (...)"
size_match = re.search(r":\s*(.*)\s*((", line)
if size_match:
backup_size = size_match.group(1).strip()
else:
else: # Fallback if format is just "Originalgröße: 13.45 GB"
backup_size = line.split(":")[1].strip()
break
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}")
@@ -428,15 +432,18 @@ class BackupManager:
"type": backup_type,
"size": backup_size,
"folder_name": item,
"full_path": full_path
"full_path": full_path,
"comment": comment
})
# Sort by parsing the date from the folder name
try:
system_backups.sort(key=lambda x: datetime.datetime.strptime(
x['date'], '%d-%B-%Y'), reverse=True)
except ValueError:
self.logger.log(
"Could not sort backups by date due to format mismatch.")
# Fallback to simple string sort if date parsing fails
system_backups.sort(key=lambda x: x['folder_name'], reverse=True)
return system_backups
@@ -459,17 +466,20 @@ class BackupManager:
if os.path.exists(info_file_path):
backup_size = "N/A"
backup_date = "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)
size_match = re.search(r":\s*(.*)\s*((", line)
if size_match:
backup_size = size_match.group(1).strip()
else:
backup_size = line.split(":")[1].strip()
elif line.strip().lower().startswith("backup-datum:"):
backup_date = line.split(":", 1)[1].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}")
@@ -478,12 +488,55 @@ class BackupManager:
"date": backup_date,
"size": backup_size,
"folder_name": item,
"full_path": full_path
"full_path": full_path,
"comment": comment
})
user_backups.sort(key=lambda x: x['folder_name'], reverse=True)
return user_backups
def get_comment(self, info_file_path: str) -> str:
"""Reads an info file and returns the comment, if it exists."""
if not os.path.exists(info_file_path):
return ""
try:
with open(info_file_path, 'r') as f:
for line in f:
if line.strip().lower().startswith("kommentar:"):
return line.split(":", 1)[1].strip()
except Exception as e:
self.logger.log(f"Error reading comment from {info_file_path}: {e}")
return ""
def update_comment(self, info_file_path: str, new_comment: str):
"""Updates the comment in a given info file."""
try:
lines = []
comment_found = False
if os.path.exists(info_file_path):
with open(info_file_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if line.strip().lower().startswith("kommentar:"):
if new_comment: # Update existing comment
new_lines.append(f"Kommentar: {new_comment}\n")
comment_found = True
# If new_comment is empty, the old line is effectively deleted
else:
new_lines.append(line)
if not comment_found and new_comment:
new_lines.append(f"Kommentar: {new_comment}\n")
with open(info_file_path, 'w') as f:
f.writelines(new_lines)
self.logger.log(f"Successfully updated comment in {info_file_path}")
except Exception as e:
self.logger.log(f"Error updating comment in {info_file_path}: {e}")
def test_pkexec_rsync(self, source_path: str, dest_path: str):
self.logger.log(f"Testing pkexec rsync command...")
command = ['pkexec', 'rsync', '-aAXHv', source_path, dest_path]

View File

@@ -571,6 +571,7 @@ class MainApplication(tk.Tk):
self.start_time = None
self.backup_is_running = False
self.actions._set_ui_state(True) # Re-enable UI
else:
# This message is not for us (likely for DataProcessing), put it back and yield.
self.queue.put(message)

View File

@@ -318,6 +318,7 @@ class Msg:
"compressed": _("Compressed"),
"encrypted": _("Encrypted"),
"bypass_security": _("Bypass security"),
"comment": _("Kommentar"),
"force_full_backup": _("Always force full backup"),
"force_incremental_backup": _("Always force incremental backup (except first)"),
"force_compression": _("Always compress backup"),

View File

@@ -301,6 +301,34 @@ class Actions:
MessageDialog(master=self.app, message_type="info",
title=Msg.STR["settings_reset_title"], text=Msg.STR["settings_reset_text"])
def _parse_size_string_to_bytes(self, size_str: str) -> int:
"""Parses a size string like '38.61 GB' into bytes."""
if not size_str or size_str == Msg.STR["calculating_size"]:
return 0
parts = size_str.split()
if len(parts) != 2:
return 0 # Invalid format
try:
value = float(parts[0])
unit = parts[1].upper()
if unit == 'B':
return int(value)
elif unit == 'KB':
return int(value * (1024**1))
elif unit == 'MB':
return int(value * (1024**2))
elif unit == 'GB':
return int(value * (1024**3))
elif unit == 'TB':
return int(value * (1024**4))
else:
return 0
except ValueError:
return 0
def _set_ui_state(self, enable: bool):
# Sidebar Buttons
for text, data in self.app.buttons_map.items():
@@ -471,7 +499,9 @@ class Actions:
# Store the path for potential deletion
self.app.current_backup_path = final_dest
source_size_bytes = self.app.left_canvas_data.get('total_bytes', 0)
# Get source size from canvas data and parse it
size_display_str = self.app.left_canvas_data.get('size', '0 B')
source_size_bytes = self._parse_size_string_to_bytes(size_display_str)
exclude_file_paths = []
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():

View File

@@ -0,0 +1,38 @@
import tkinter as tk
from tkinter import ttk
class CommentEditorDialog(tk.Toplevel):
def __init__(self, master, info_file_path, backup_manager):
super().__init__(master)
self.info_file_path = info_file_path
self.backup_manager = backup_manager
self.title("Kommentar bearbeiten")
self.geometry("400x300")
main_frame = ttk.Frame(self, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
self.text_widget = tk.Text(main_frame, wrap="word", height=10, width=40)
self.text_widget.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="Speichern & Schließen", command=self._save_and_close).pack(side=tk.RIGHT)
ttk.Button(button_frame, text="Abbrechen", command=self.destroy).pack(side=tk.RIGHT, padx=5)
self._load_comment()
self.transient(master)
self.grab_set()
self.wait_window(self)
def _load_comment(self):
comment = self.backup_manager.get_comment(self.info_file_path)
self.text_widget.insert("1.0", comment)
def _save_and_close(self):
new_comment = self.text_widget.get("1.0", tk.END).strip()
self.backup_manager.update_comment(self.info_file_path, new_comment)
self.destroy()

View File

@@ -3,6 +3,7 @@ from tkinter import ttk
import os
from pbp_app_config import Msg
from pyimage_ui.comment_editor_dialog import CommentEditorDialog
class SystemBackupContentFrame(ttk.Frame):
@@ -17,7 +18,7 @@ class SystemBackupContentFrame(ttk.Frame):
self, text=Msg.STR["backup_content"], padding=10)
self.content_frame.pack(fill=tk.BOTH, expand=True)
columns = ("date", "type", "size", "folder_name")
columns = ("date", "type", "size", "comment", "folder_name")
self.content_tree = ttk.Treeview(
self.content_frame, columns=columns, show="headings")
self.content_tree.heading(
@@ -26,22 +27,35 @@ class SystemBackupContentFrame(ttk.Frame):
"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("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)
ttk.Button(list_button_frame, text=Msg.STR["restore"],
command=self._restore_selected).pack(side=tk.LEFT, padx=5)
ttk.Button(list_button_frame, text=Msg.STR["delete"],
command=self._delete_selected).pack(side=tk.LEFT, padx=5)
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:
@@ -67,8 +81,37 @@ class SystemBackupContentFrame(ttk.Frame):
backup_info.get("date", "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")
))
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 = self.content_tree.focus()
if not selected_item:
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")
# 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):
# Placeholder for restore logic

View File

@@ -3,6 +3,7 @@ from tkinter import ttk
import os
from pbp_app_config import Msg
from pyimage_ui.comment_editor_dialog import CommentEditorDialog
class UserBackupContentFrame(ttk.Frame):
@@ -17,28 +18,37 @@ class UserBackupContentFrame(ttk.Frame):
self, text=Msg.STR["backup_content"], padding=10)
self.content_frame.pack(fill=tk.BOTH, expand=True)
columns = ("date", "size", "folder_name")
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)
ttk.Button(list_button_frame, text=Msg.STR["restore"],
command=self._restore_selected).pack(side=tk.LEFT, padx=5)
ttk.Button(list_button_frame, text=Msg.STR["delete"],
command=self._delete_selected).pack(side=tk.LEFT, padx=5)
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:
@@ -61,8 +71,34 @@ class UserBackupContentFrame(ttk.Frame):
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