Files
Py-Backup/pyimage_ui/actions.py
Désiré Werner Menrath 988b0e8d1d fix(app): Behebt das Hängenbleiben der UI und die falsche Grössenberechnung
Mehrere grundlegende Probleme in der Anwendungslogik wurden behoben:

- **UI-Verarbeitungsschleife:** Die Verarbeitung von Nachrichten aus Hintergrund-Threads wurde komplett überarbeitet. Zuvor führten zwei konkurrierende Schleifen zu einer Race Condition, bei der Nachrichten verloren gingen. Jetzt gibt es eine einzige, zentrale Verarbeitungsschleife, die Nachrichten in Stapeln verarbeitet. Dies behebt das Problem, dass die Benutzeroberfläche nach dem Löschen oder dem Abschluss eines Backups im "in Arbeit"-Zustand hängen blieb.

- **Backup-Grössenberechnung:** Die Ermittlung der Grösse von inkrementellen Backups wurde robuster gestaltet.
    - Die rsync-Ausgabe wird nun zuverlässig auf Englisch erzwungen, um Parsing-Fehler in anderen System-Locales zu vermeiden.
    - Die Grösse wird nun aus der `sent... received...` Zusammenfassungszeile von rsync ausgelesen, was auch bei Backups ohne Datenänderungen einen Wert ungleich Null liefert.
    - Es wird nun korrekt zwischen Voll-Backups (Anzeige der Gesamtgrösse) und inkrementellen Backups (Anzeige der Übertragungsgrösse) unterschieden.

- **Sonstige Korrekturen:**
    - Eine fehlende Übersetzung für die manuelle Ausschlussliste wurde hinzugefügt.
    - Ein überflüssiger Aufruf zum Starten der Verarbeitungsschleife wurde entfernt.
2025-09-02 13:59:06 +02:00

701 lines
30 KiB
Python

# pyimage_ui/actions.py
import tkinter as tk
import os
import shutil
import threading
import datetime
import locale
from typing import Optional
from shared_libs.message import MessageDialog
from shared_libs.custom_file_dialog import CustomFileDialog
from pbp_app_config import AppConfig, Msg
from shared_libs.logger import app_logger
from shared_libs.common_tools import message_box_animation
class Actions:
def __init__(self, app):
self.app = app
def _set_backup_type(self, backup_type: str):
if backup_type == "full":
self.app.vollbackup_var.set(True)
self.app.inkrementell_var.set(False)
self.app.full_backup_cb.config(state="disabled")
self.app.incremental_cb.config(state="disabled")
self.app.accurate_size_cb.config(state="disabled")
elif backup_type == "incremental":
self.app.vollbackup_var.set(False)
self.app.inkrementell_var.set(True)
self.app.full_backup_cb.config(state="normal")
self.app.incremental_cb.config(state="normal")
self.app.accurate_size_cb.config(state="normal")
def _update_backup_type_controls(self):
"""
Updates the state of the Full/Incremental backup radio buttons based on
advanced settings and the content of the destination folder.
This logic only applies to 'Computer' backups.
"""
# Do nothing if the backup mode is not 'backup' or source is not 'Computer'
if self.app.mode != 'backup' or self.app.left_canvas_data.get('folder') != "Computer":
self.app.full_backup_cb.config(state="normal")
self.app.incremental_cb.config(state="normal")
return
# Respect that advanced settings might have already disabled the controls
# This check is based on the user's confirmation that this logic exists elsewhere
if self.app.full_backup_cb.cget('state') == 'disabled':
return
# --- Standard Logic ---
full_backup_exists = False
if self.app.destination_path and os.path.isdir(self.app.destination_path):
system_backups = self.app.backup_manager.list_system_backups(
self.app.destination_path)
for backup in system_backups:
if backup.get('type') == 'Full':
full_backup_exists = True
break
if full_backup_exists:
self._set_backup_type("incremental")
else:
self._set_backup_type("full")
def handle_backup_type_change(self, changed_var_name):
if changed_var_name == 'voll':
if self.app.vollbackup_var.get():
self._set_backup_type("full")
elif changed_var_name == 'inkrementell':
if self.app.inkrementell_var.get():
self._set_backup_type("incremental")
def handle_compression_change(self):
if self.app.compressed_var.get():
# Compression is enabled, force full backup
self.app.vollbackup_var.set(True)
self.app.inkrementell_var.set(False)
self.app.full_backup_cb.config(state="disabled")
self.app.incremental_cb.config(state="disabled")
# Also disable accurate incremental size calculation, as it's irrelevant
self.app.accurate_size_cb.config(state="disabled")
self.app.genaue_berechnung_var.set(False)
else:
# Compression is disabled, restore normal logic
self.app.full_backup_cb.config(state="normal")
# The state of the incremental checkbox depends on whether a full backup exists
self._update_backup_type_controls()
def on_toggle_accurate_size_calc(self):
# This method is called when the user clicks the "Genaue inkrem. Größe" checkbox.
if not self.app.genaue_berechnung_var.get():
# Box was unchecked by user, but this shouldn't happen if disabled.
# Or it was unchecked automatically after a run. Do nothing.
return
# If a normal calculation is running, stop it.
if self.app.calculation_thread and self.app.calculation_thread.is_alive():
self.app.calculation_stop_event.set()
app_logger.log("Stopping previous size calculation.")
# --- Start the accurate calculation ---
app_logger.log("Accurate incremental size calculation requested.")
self.app.accurate_calculation_running = True
# Lock the UI, keep Cancel enabled
self._set_ui_state(False, keep_cancel_enabled=True)
self.app.start_pause_button.config(text=Msg.STR["cancel_backup"])
# Immediately clear the projection canvases
self.app.drawing.reset_projection_canvases()
# Update canvas to show it's calculating and start animation
self.app.info_label.config(
text=Msg.STR["please_wait"], foreground="#0078d7")
self.app.task_progress.config(mode="indeterminate")
self.app.task_progress.start()
self.app.left_canvas_data.update({
'size': Msg.STR["calculating_size"],
'calculating': True,
})
self.app.drawing.start_backup_calculation_display()
self.app.animated_icon.start()
# Get folder path from the current canvas data
folder_path = self.app.left_canvas_data.get('path_display')
button_text = self.app.left_canvas_data.get('folder')
if not folder_path or not button_text:
app_logger.log(
"Cannot start accurate calculation, source folder info missing.")
self._set_ui_state(True) # Unlock UI
self.app.genaue_berechnung_var.set(False) # Uncheck the box
self.app.accurate_calculation_running = False
self.app.animated_icon.stop("DISABLE")
return
def threaded_incremental_calc():
status = 'failure' # Default to failure
size = 0
try:
exclude_file_paths = []
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(
AppConfig.GENERATED_EXCLUDE_LIST_PATH)
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(AppConfig.USER_EXCLUDE_LIST_PATH)
if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(AppConfig.MANUAL_EXCLUDE_LIST_PATH)
base_dest = self.app.destination_path
correct_parent_dir = os.path.join(base_dest, "pybackup")
dummy_dest_for_calc = os.path.join(
correct_parent_dir, "dummy_name")
size = self.app.data_processing.get_incremental_backup_size(
source_path=folder_path,
dest_path=dummy_dest_for_calc,
is_system=True,
exclude_files=exclude_file_paths
)
# If rsync fails, size is 0, so status will be failure.
status = 'success' if size > 0 else 'failure'
except Exception as e:
app_logger.log(f"Error during threaded_incremental_calc: {e}")
status = 'failure'
finally:
# This message MUST be sent to unlock the UI and update the display.
# It is now handled by the main data_processing queue.
if self.app.accurate_calculation_running: # Only post if not cancelled
self.app.queue.put(
(button_text, size, self.app.mode, 'accurate_incremental', status))
self.app.calculation_thread = threading.Thread(
target=threaded_incremental_calc)
self.app.calculation_thread.daemon = True
self.app.calculation_thread.start()
def on_sidebar_button_click(self, button_text):
if self.app.backup_is_running or self.app.accurate_calculation_running:
app_logger.log(
"Action blocked: Backup or accurate calculation is in progress.")
return
self.app.drawing.reset_projection_canvases()
if not self.app.canvas_frame.winfo_viewable():
self.app.navigation.toggle_mode(
self.app.mode, trigger_calculation=False)
self.app.log_window.clear_log()
# Reverse map from translated UI string to canonical key
REVERSE_FOLDER_MAP = {
"Computer": "Computer",
Msg.STR["cat_documents"]: "Documents",
Msg.STR["cat_images"]: "Pictures",
Msg.STR["cat_music"]: "Music",
Msg.STR["cat_videos"]: "Videos",
}
canonical_key = REVERSE_FOLDER_MAP.get(button_text, button_text)
folder_path = AppConfig.FOLDER_PATHS.get(canonical_key)
if not folder_path or not folder_path.exists():
print(
f"Folder not found for {canonical_key} (Path: {folder_path})")
self.app.start_pause_button.config(state="disabled")
return
if self.app.mode == 'restore':
try:
total, used, free = shutil.disk_usage(str(folder_path))
self.app.destination_total_bytes = total
self.app.destination_used_bytes = used
except FileNotFoundError:
app_logger.log(f"Path not found for disk usage: {folder_path}")
return
icon_name = self.app.buttons_map[button_text]['icon']
# Determine the correct description based on mode and selection
extra_info = ""
if button_text == "Computer":
if self.app.mode == 'backup':
extra_info = Msg.STR["system_backup_info"]
else: # restore
extra_info = Msg.STR["system_restore_info"]
else: # User folder
if self.app.mode == 'backup':
extra_info = Msg.STR["user_backup_info"]
else: # restore
extra_info = Msg.STR["user_restore_info"]
# CORRECTED LOGIC ORDER:
# 1. Update backup type checkboxes BEFORE starting the calculation.
if self.app.mode == 'backup':
self._update_backup_type_controls()
else: # restore mode
self.app.config_manager.set_setting(
"restore_destination_path", folder_path)
# 2. Unified logic for starting a calculation on the left canvas.
self._start_left_canvas_calculation(
button_text, str(folder_path), icon_name, extra_info)
def _start_left_canvas_calculation(self, button_text, folder_path, icon_name, extra_info):
self.app.start_pause_button.config(state="disabled")
if self.app.mode == 'backup' and not self.app.destination_path:
self.app.left_canvas_data.update({
'icon': icon_name,
'folder': button_text,
'path_display': folder_path,
'size': Msg.STR["select_destination_first"],
'calculating': False,
'extra_info': extra_info
})
self.app.drawing.redraw_left_canvas()
return
if self.app.mode == 'restore' and not self.app.right_canvas_data.get('path_display'):
self.app.left_canvas_data.update({
'icon': icon_name,
'folder': button_text,
'path_display': folder_path,
'size': Msg.STR["select_source_first"],
'calculating': False,
'extra_info': extra_info
})
self.app.drawing.redraw_left_canvas()
return
if self.app.calculation_thread and self.app.calculation_thread.is_alive():
self.app.calculation_stop_event.set()
data_dict = self.app.left_canvas_data
data_dict.update({
'icon': icon_name,
'folder': button_text,
'path_display': folder_path,
'size': Msg.STR["calculating_size"],
'calculating': True,
'extra_info': extra_info
})
self.app.drawing.start_backup_calculation_display()
self.app.calculation_stop_event = threading.Event()
# For system backup, now we default to the simple, fast (but for incremental inaccurate) full size calculation.
# The accurate calculation is now triggered explicitly by the new checkbox.
if button_text == "Computer":
app_logger.log(
"Using default (full) size calculation for system backup display.")
exclude_patterns = self.app.data_processing.load_exclude_patterns()
target_method = self.app.data_processing.get_folder_size_threaded
args = (folder_path, button_text, self.app.calculation_stop_event,
exclude_patterns, self.app.mode)
else:
# For user folders, do not use any exclusions
target_method = self.app.data_processing.get_user_folder_size_threaded
args = (folder_path, button_text,
self.app.calculation_stop_event, self.app.mode)
self.app.calculation_thread = threading.Thread(
target=target_method, args=args)
self.app.calculation_thread.daemon = True
self.app.calculation_thread.start()
if self.app.mode == 'backup':
self._update_backup_type_controls()
else: # restore mode
self.app.config_manager.set_setting(
"restore_destination_path", folder_path)
def on_right_canvas_click(self, event):
if self.app.backup_is_running:
app_logger.log("Action blocked: Backup is in progress.")
return
self.app.drawing.reset_projection_canvases()
self.app.after(100, self._open_source_or_destination_dialog)
def _open_source_or_destination_dialog(self):
title = Msg.STR["select_dest_folder_title"] if self.app.mode == 'backup' else Msg.STR["select_restore_source_title"]
path = self._get_path_from_dialog(title)
if path:
self._update_right_canvas_info(path)
def _get_path_from_dialog(self, title) -> Optional[str]:
self.app.update_idletasks()
dialog = CustomFileDialog(self.app, mode="dir", title=title)
self.app.wait_window(dialog)
path = dialog.get_result()
dialog.destroy()
return path
def _update_right_canvas_info(self, path):
try:
if self.app.mode == "backup":
self.app.destination_path = path
backup_root_to_exclude = f"/{path.strip('/').split('/')[0]}"
try:
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'r+') as f:
lines = f.readlines()
f.seek(0)
if not any(backup_root_to_exclude in line for line in lines):
lines.append(f"\n{backup_root_to_exclude}\n")
f.writelines(lines)
except FileNotFoundError:
with open(AppConfig.USER_EXCLUDE_LIST_PATH, 'w') as f:
f.write(f"{backup_root_to_exclude}\n")
except IOError as e:
app_logger.log(f"Error updating exclusion list: {e}")
total, used, free = shutil.disk_usage(path)
self.app.destination_total_bytes = total
self.app.destination_used_bytes = used
size_str = f"{used / (1024**3):.2f} GB / {total / (1024**3):.2f} GB"
self.app.right_canvas_data.update({
'folder': os.path.basename(path.rstrip('/')),
'path_display': path,
'size': size_str
})
self.app.config_manager.set_setting(
"backup_destination_path", path)
self.app.drawing.redraw_right_canvas()
self.app.drawing.update_target_projection()
current_source = self.app.left_canvas_data.get('folder')
if current_source:
self.on_sidebar_button_click(current_source)
self._update_backup_type_controls()
elif self.app.mode == "restore":
self.app.right_canvas_data.update({
'folder': os.path.basename(path.rstrip('/')),
'path_display': path,
'size': ''
})
self.app.config_manager.set_setting(
"restore_source_path", path)
self.app.drawing.calculate_restore_folder_size()
self.app.start_pause_button.config(state="normal")
except FileNotFoundError:
with message_box_animation(self.app.calculating_animation):
MessageDialog(master=self.app, message_type="error",
title=Msg.STR["error"], text=Msg.STR["path_not_found"].format(path=path)).show()
def reset_to_default_settings(self):
try:
AppConfig.create_default_user_excludes()
except OSError as e:
app_logger.log(f"Error creating default user exclude list: {e}")
self.app.config_manager.set_setting("backup_destination_path", None)
self.app.config_manager.set_setting("restore_source_path", None)
self.app.config_manager.set_setting("restore_destination_path", None)
# Remove advanced settings
self.app.config_manager.remove_setting("backup_animation_type")
self.app.config_manager.remove_setting("calculation_animation_type")
self.app.config_manager.remove_setting("force_full_backup")
self.app.config_manager.remove_setting("force_incremental_backup")
self.app.config_manager.remove_setting("force_compression")
self.app.config_manager.remove_setting("force_encryption")
# Update the main UI to reflect the cleared settings
self.app.update_backup_options_from_config()
AppConfig.generate_and_write_final_exclude_list()
app_logger.log("Settings have been reset to default values.")
settings_frame = self.app.settings_frame
if settings_frame:
settings_frame.load_and_display_excludes()
settings_frame._load_hidden_files()
self.app.destination_path = None
self.app.start_pause_button.config(state="disabled")
# Clear the canvases and reset the UI to its initial state for the current mode
self.app.backup_left_canvas_data.clear()
self.app.backup_right_canvas_data.clear()
self.app.restore_left_canvas_data.clear()
self.app.restore_right_canvas_data.clear()
current_source = self.app.left_canvas_data.get('folder')
if current_source:
self.on_sidebar_button_click(current_source)
self.app.backup_content_frame.system_backups_frame._load_backup_content()
self.app.backup_content_frame.user_backups_frame._load_backup_content()
with message_box_animation(self.app.animated_icon):
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, keep_cancel_enabled: bool = False, allow_log_and_backup_toggle: bool = False):
# Sidebar Buttons
for text, data in self.app.buttons_map.items():
# Find the actual button widget in the sidebar_buttons_frame
# This assumes the order of creation is consistent or we can identify by text
# A more robust way would be to store references to the buttons in a dict in MainApplication
# For now, let's iterate through children and match text
for child in self.app.sidebar_buttons_frame.winfo_children():
if isinstance(child, tk.ttk.Button) and child.cget("text") == text:
child.config(state="normal" if enable else "disabled")
break
# Schedule and Settings buttons in sidebar
self.app.schedule_dialog_button.config(
state="normal" if enable else "disabled")
self.app.settings_button.config(
state="normal" if enable else "disabled")
# Mode Button (arrow between canvases)
self.app.mode_button.config(state="normal" if enable else "disabled")
# Top Navigation Buttons
for i, button in enumerate(self.app.nav_buttons):
if allow_log_and_backup_toggle and self.app.nav_buttons_defs[i][0] in [Msg.STR["log"], Msg.STR["backup_menu"]]:
continue
button.config(state="normal" if enable else "disabled")
# Right Canvas (Destination/Restore Source)
if enable:
self.app.right_canvas.bind(
"<Button-1>", self.app.actions.on_right_canvas_click)
self.app.right_canvas.config(cursor="hand2")
else:
self.app.right_canvas.unbind("<Button-1>")
self.app.right_canvas.config(cursor="")
# Checkboxes in the task bar
if enable:
# When enabling, re-run the logic that sets the correct state
# for all checkboxes based on config and context.
self.app.update_backup_options_from_config()
self.app.actions._update_backup_type_controls()
else:
# When disabling, just disable all of them.
checkboxes = [
self.app.full_backup_cb,
self.app.incremental_cb,
self.app.accurate_size_cb,
self.app.compressed_cb,
self.app.encrypted_cb,
self.app.test_run_cb,
self.app.bypass_security_cb
]
for cb in checkboxes:
cb.config(state="disabled")
# Special handling for the cancel button during accurate calculation
if keep_cancel_enabled:
self.app.start_pause_button.config(state="normal")
def toggle_start_cancel(self):
# If an accurate calculation is running, cancel it.
if self.app.accurate_calculation_running:
app_logger.log("Accurate size calculation cancelled by user.")
self.app.accurate_calculation_running = False
self.app.genaue_berechnung_var.set(False)
# Stop all animations and restore UI
self.app.animated_icon.stop("DISABLE")
if self.app.left_canvas_animation:
self.app.left_canvas_animation.stop()
self.app.left_canvas_data['calculating'] = False
self.app.drawing.redraw_left_canvas()
# Restore bottom action bar
self.app.task_progress.stop()
self.app.task_progress.config(mode="determinate", value=0)
self.app.info_label.config(
# Orange for cancelled
text=Msg.STR["accurate_calc_cancelled"], foreground="#E8740C")
self.app.start_pause_button.config(text=Msg.STR["start"])
self._set_ui_state(True)
return
# If a backup is already running, we must be cancelling.
if self.app.backup_is_running:
self.app.animated_icon.stop("DISABLE")
delete_path = getattr(self.app, 'current_backup_path', None)
is_system_backup = self.app.backup_manager.is_system_process
if is_system_backup:
if delete_path:
self.app.backup_manager.cancel_and_delete_privileged_backup(
delete_path)
app_logger.log(
Msg.STR["backup_cancelled_and_deleted_msg"])
self.app.info_label.config(
text=Msg.STR["backup_cancelled_and_deleted_msg"])
else:
self.app.backup_manager.cancel_backup()
app_logger.log(
"Backup cancelled, but directory could not be deleted (path unknown).")
self.app.info_label.config(
text="Backup cancelled, but directory could not be deleted (path unknown).")
else:
self.app.backup_manager.cancel_backup()
if delete_path:
try:
app_logger.log(
f"Attempting to delete non-privileged path: {delete_path}")
if os.path.isdir(delete_path):
shutil.rmtree(delete_path)
app_logger.log(
Msg.STR["backup_cancelled_and_deleted_msg"])
self.app.info_label.config(
text=Msg.STR["backup_cancelled_and_deleted_msg"])
except Exception as e:
app_logger.log(f"Error deleting backup directory: {e}")
self.app.info_label.config(
text=f"Error deleting backup directory: {e}")
else:
app_logger.log(
"Backup cancelled, but no path found to delete.")
self.app.info_label.config(
text="Backup cancelled, but no path found to delete.")
if hasattr(self.app, 'current_backup_path'):
self.app.current_backup_path = None
# Reset state
self.app.backup_is_running = False
self.app.start_pause_button["text"] = Msg.STR["start"]
self._set_ui_state(True)
# Otherwise, we are starting a new backup.
else:
if self.app.start_pause_button['state'] == 'disabled':
return
self.app.backup_is_running = True
# --- Record and Display Start Time ---
self.app.start_time = datetime.datetime.now()
start_str = self.app.start_time.strftime("%H:%M:%S")
self.app.start_time_label.config(text=f"Start: {start_str}")
self.app.end_time_label.config(text="Ende: --:--:--")
self.app.duration_label.config(text="Dauer: --:--:--")
self.app.info_label.config(text="Backup wird vorbereitet...")
# --- End Time Logic ---
self.app.start_pause_button["text"] = Msg.STR["cancel_backup"]
self.app.update_idletasks()
self.app.log_window.clear_log()
self._set_ui_state(False, allow_log_and_backup_toggle=True)
self.app.animated_icon.start()
if self.app.mode == "backup":
source_size_bytes = self.app.source_size_bytes
if self.app.vollbackup_var.get():
self._start_system_backup("full", source_size_bytes)
else:
self._start_system_backup("incremental", source_size_bytes)
else:
pass
def _start_system_backup(self, mode, source_size_bytes):
base_dest = self.app.destination_path
if not base_dest:
MessageDialog(master=self.app, message_type="error",
title=Msg.STR["error"], text=Msg.STR["err_no_dest_folder"])
return
if base_dest.startswith("/home"):
with message_box_animation(self.app.animated_icon):
MessageDialog(master=self.app, message_type="error",
title=Msg.STR["error"], text=Msg.STR["system_backup_in_home_error"])
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-%B-%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)
# 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)
exclude_file_paths = []
if AppConfig.GENERATED_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(AppConfig.GENERATED_EXCLUDE_LIST_PATH)
if AppConfig.USER_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(AppConfig.USER_EXCLUDE_LIST_PATH)
if AppConfig.MANUAL_EXCLUDE_LIST_PATH.exists():
exclude_file_paths.append(AppConfig.MANUAL_EXCLUDE_LIST_PATH)
is_dry_run = self.app.testlauf_var.get()
is_compressed = self.app.compressed_var.get()
self.app.backup_manager.start_backup(
queue=self.app.queue,
source_path="/",
dest_path=final_dest,
is_system=True,
is_dry_run=is_dry_run,
exclude_files=exclude_file_paths,
source_size=source_size_bytes,
is_compressed=is_compressed)
def _start_user_backup(self, sources):
dest = self.app.destination_path
if not dest:
MessageDialog(master=self.app, message_type="error",
title=Msg.STR["error"], text=Msg.STR["err_no_dest_folder"])
return
is_dry_run = self.app.testlauf_var.get()
for source in sources:
self.app.backup_manager.start_backup(
queue=self.app.queue,
source_path=source,
dest_path=dest,
is_system=False,
is_dry_run=is_dry_run)