new method to detect backup

This commit is contained in:
2025-09-08 21:02:03 +02:00
parent 95a55a7d4c
commit 798134dd20
6 changed files with 689 additions and 348 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -136,16 +136,20 @@ class EncryptionManager:
def is_encrypted(self, base_dest_path: str) -> bool:
return os.path.exists(self.get_container_path(base_dest_path))
def get_mount_point(self, base_dest_path: str) -> str:
"""Constructs the unique, static mount point path for a given destination."""
username = os.path.basename(base_dest_path.rstrip('/'))
mapper_name = f"pybackup_luks_{username}"
return os.path.join("/mnt", mapper_name)
def is_mounted(self, base_dest_path: str) -> bool:
pybackup_dir = os.path.join(base_dest_path, "pybackup")
mount_point = os.path.join(pybackup_dir, "encrypted")
mount_point = self.get_mount_point(base_dest_path)
return os.path.ismount(mount_point) or base_dest_path in self.mounted_destinations
def mount_for_deletion(self, base_dest_path: str, is_system: bool, password: str) -> Optional[str]:
self.logger.log("Mounting container for deletion operation.")
if self._open_and_mount(base_dest_path, is_system, password):
mount_point = os.path.join(os.path.dirname(
self.get_container_path(base_dest_path)), "..", "encrypted")
mount_point = self.get_mount_point(base_dest_path)
self.mounted_destinations.add(base_dest_path)
return mount_point
self.logger.log("Failed to mount container for deletion.")
@@ -162,8 +166,7 @@ class EncryptionManager:
self.logger.log("Handling existing container.")
username = os.path.basename(base_dest_path.rstrip('/'))
mount_point = os.path.join(os.path.dirname(
self.get_container_path(base_dest_path)), "..", "encrypted")
mount_point = self.get_mount_point(base_dest_path)
if not self.is_mounted(base_dest_path):
if not self._open_and_mount(base_dest_path, is_system):
@@ -232,8 +235,7 @@ mount \"/dev/mapper/{mapper_name}\" \"{mount_point}\"
return None
container_path = self.get_container_path(base_dest_path)
mount_point = os.path.join(os.path.dirname(
container_path), "..", "encrypted")
mount_point = self.get_mount_point(base_dest_path)
mapper_name = f"pybackup_luks_{username}"
chown_cmd = self._get_chown_command(mount_point, is_system)
@@ -263,8 +265,7 @@ mount \"/dev/mapper/{mapper_name}\" \"{mount_point}\"
return False
container_path = self.get_container_path(base_dest_path)
mount_point = os.path.join(os.path.dirname(
container_path), "..", "encrypted")
mount_point = self.get_mount_point(base_dest_path)
mapper_name = f"pybackup_luks_{username}"
chown_cmd = self._get_chown_command(mount_point, is_system)
@@ -291,9 +292,7 @@ mount \"/dev/mapper/{mapper_name}\" \"{mount_point}\"
return
self.logger.log(f"Unmounting and resetting owner for {base_dest_path}")
container_path = self.get_container_path(base_dest_path)
mount_point = os.path.join(os.path.dirname(
container_path), "..", "encrypted")
mount_point = self.get_mount_point(base_dest_path)
script = f"""
chown root:root \"{mount_point}\" || true

View File

@@ -361,6 +361,6 @@ class Msg:
"keyfile_settings": _("Keyfile Settings"), # New
"backup_defaults_title": _("Backup Defaults"), # New
"automation_settings_title": _("Automation Settings"), # New
"create_add_key_file": _("Create/Add Key File"), # New
"key_file_not_created": _("Key file not created."), # New
"create_add_key_file": _("Create/Add Key File"), # New
"key_file_not_created": _("Key file not created."), # New
}

View File

@@ -678,6 +678,9 @@ class MainApplication(tk.Tk):
self.task_progress.stop()
elif message_type == 'cancel_button_state':
self.start_pause_button.config(state=value)
elif message_type == 'current_path':
self.current_backup_path = value
app_logger.log(f"Set current backup path to: {value}")
elif message_type == 'deletion_complete':
self.actions._set_ui_state(True)
self.backup_content_frame.hide_deletion_status()

View File

@@ -27,41 +27,46 @@ class Actions:
self.app.inkrementell_var.set(True)
def _update_backup_type_controls(self):
# Only applies to system backups in backup mode
if self.app.mode != 'backup' or self.app.left_canvas_data.get('folder') != "Computer":
self._set_backup_type("full") # Default for user backups
self.app.full_backup_cb.config(state='disabled')
self.app.incremental_cb.config(state='disabled')
return
else:
# Re-enable if we switch back to system backup
source_name = self.app.left_canvas_data.get('folder')
is_system_backup = (source_name == "Computer")
# Re-enable controls for user backups, disable for system unless conditions are met
if not is_system_backup:
self.app.full_backup_cb.config(state='normal')
self.app.incremental_cb.config(state='normal')
else: # System backup
self.app.full_backup_cb.config(state='normal')
self.app.incremental_cb.config(state='normal')
# If controls are forced by advanced settings, do nothing
if self.app.full_backup_cb.cget('state') == 'disabled' and self.app.incremental_cb.cget('state') == 'disabled':
# Handle forced settings from advanced config, which have top priority
if self.app.config_manager.get_setting("force_full_backup", False):
self._set_backup_type("full")
self.app.full_backup_cb.config(state='disabled')
self.app.incremental_cb.config(state='disabled')
return
if self.app.config_manager.get_setting("force_incremental_backup", False):
self._set_backup_type("incremental")
self.app.full_backup_cb.config(state='disabled')
self.app.incremental_cb.config(state='disabled')
return
full_backup_exists = False
if self.app.destination_path and os.path.isdir(self.app.destination_path):
pybackup_dir = os.path.join(self.app.destination_path, "pybackup")
if not os.path.isdir(pybackup_dir):
self._set_backup_type("full")
return
# Default to Full if no destination is set
if not self.app.destination_path or not os.path.isdir(self.app.destination_path):
self._set_backup_type("full")
return
is_encrypted_backup = self.app.encrypted_var.get()
is_encrypted = self.app.encrypted_var.get()
# Use the new detection logic for both user and system backups
# Note: For system backups, source_name is "Computer". We might need a more specific profile name.
# For now, we adapt it to the check_for_full_backup function's expectation.
profile_name = "system" if is_system_backup else source_name
system_backups = self.app.backup_manager.list_system_backups(
self.app.destination_path, mount_if_needed=False)
if system_backups is None: # Encrypted, but not inspected
full_backup_exists = True # Assume one exists to be safe
else:
for backup in system_backups:
# Match the encryption status and check if it's a full backup
if backup.get('is_encrypted') == is_encrypted_backup and backup.get('backup_type_base') == 'Full':
full_backup_exists = True
break
full_backup_exists = self.app.backup_manager.check_for_full_backup(
dest_path=self.app.destination_path,
source_name=profile_name, # Using a profile name now
is_encrypted=is_encrypted
)
if full_backup_exists:
self._set_backup_type("incremental")
@@ -643,32 +648,7 @@ class Actions:
return
is_encrypted = self.app.encrypted_var.get()
password = None
if is_encrypted:
username = os.path.basename(base_dest.rstrip('/'))
password = self.app.backup_manager.encryption_manager.get_password(
username, confirm=True)
if not password:
app_logger.log(
"Encryption enabled, but no password provided. Aborting backup.")
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
return
try:
locale.setlocale(locale.LC_TIME, 'de_DE.UTF-8')
except locale.Error:
app_logger.log(
"Could not set locale to de_DE.UTF-8. Using default.")
now = datetime.datetime.now()
date_str = now.strftime("%d-%m-%Y")
time_str = now.strftime("%H:%M:%S")
folder_name = f"{date_str}_{time_str}_system_{mode}"
# The backup_manager will add /pybackup/
final_dest = os.path.join(base_dest, folder_name)
self.app.current_backup_path = final_dest
# Password handling is now managed within the backup manager if needed for mounting
source_size_bytes = self.app.left_canvas_data.get('total_bytes', 0)
@@ -686,8 +666,9 @@ class Actions:
self.app.backup_manager.start_backup(
queue=self.app.queue,
source_path="/",
dest_path=final_dest,
dest_path=base_dest, # Pass the base destination path
is_system=True,
source_name="system",
is_dry_run=is_dry_run,
exclude_files=exclude_file_paths,
source_size=source_size_bytes,
@@ -710,30 +691,10 @@ class Actions:
return
is_encrypted = self.app.encrypted_var.get()
password = None
if is_encrypted:
username = os.path.basename(base_dest.rstrip('/'))
password = self.app.backup_manager.encryption_manager.get_password(
username, confirm=True)
if not password:
app_logger.log(
"Encryption enabled, but no password provided. Aborting backup.")
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
return
# Password handling is now managed within the backup manager if needed for mounting
# Determine mode for user backup based on UI selection
mode = "full" if self.app.vollbackup_var.get() else "incremental"
now = datetime.datetime.now()
date_str = now.strftime("%d-%m-%Y")
time_str = now.strftime("%H:%M:%S")
folder_name = f"{date_str}_{time_str}_user_{source_name}_{mode}"
final_dest = os.path.join(base_dest, folder_name)
self.app.current_backup_path = final_dest
is_dry_run = self.app.testlauf_var.get()
is_compressed = self.app.compressed_var.get()
use_trash_bin = self.app.config_manager.get_setting(
@@ -744,10 +705,11 @@ class Actions:
self.app.backup_manager.start_backup(
queue=self.app.queue,
source_path=source_path,
dest_path=final_dest,
dest_path=base_dest, # Pass the base destination path
is_system=False,
source_name=source_name,
is_dry_run=is_dry_run,
exclude_files=None,
exclude_files=None, # User backups don't use the global exclude list here
source_size=source_size_bytes,
is_compressed=is_compressed,
is_encrypted=is_encrypted,

View File

@@ -94,15 +94,15 @@ class UserBackupContentFrame(ttk.Frame):
if not selected_backup:
return
is_encrypted = selected_backup.get('is_encrypted', False)
info_file_name = f"{selected_item_id}{'_encrypted' if is_encrypted else ''}.txt"
info_file_path = os.path.join(
self.backup_path, "pybackup", info_file_name)
if not os.path.exists(info_file_path):
self.backup_manager.update_comment(info_file_path, "")
# Use the direct path to the info file, which we added to the backup dict
info_file_path = selected_backup.get('info_file_path')
if not info_file_path or not os.path.isfile(info_file_path):
MessageDialog(self, message_type="error", title="Error", text=f"Metadata file not found: {info_file_path}")
return
CommentEditorDialog(self, info_file_path, self.backup_manager)
# Refresh the view to show the new comment
self.parent_view.show(self.backup_path)
def _restore_selected(self):
@@ -129,28 +129,24 @@ class UserBackupContentFrame(ttk.Frame):
password = None
if is_encrypted:
username = os.path.basename(self.backup_path.rstrip('/'))
# Get password in the UI thread before starting the background task
password = self.backup_manager.encryption_manager.get_password(
username, confirm=False)
# For encrypted backups, the base_dest_path is the path to the container's parent directory
# We assume the logic to get the username/keyring entry is handled by the encryption manager
password = self.backup_manager.encryption_manager.get_password(confirm=False)
if not password:
self.actions.logger.log(
"Password entry cancelled, aborting deletion.")
return
info_file_to_delete = os.path.join(
self.backup_path, "pybackup", f"{selected_item_id}{'_encrypted' if is_encrypted else ''}.txt")
self.actions._set_ui_state(False)
self.parent_view.show_deletion_status(
Msg.STR["deleting_backup_in_progress"])
# The info_file_path is no longer needed as it's inside the folder_to_delete
self.backup_manager.start_delete_backup(
path_to_delete=folder_to_delete,
info_file_path=info_file_to_delete,
is_encrypted=is_encrypted,
is_system=False,
base_dest_path=self.backup_path,
base_dest_path=self.backup_path, # This is the root destination folder
password=password,
queue=self.winfo_toplevel().queue
)