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:
@@ -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]
|
||||
|
@@ -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)
|
||||
|
@@ -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"),
|
||||
|
@@ -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():
|
||||
|
38
pyimage_ui/comment_editor_dialog.py
Normal file
38
pyimage_ui/comment_editor_dialog.py
Normal 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()
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user