Die Logik zur Erkennung von vollständigen Backups wurde verbessert. Das Programm prüft nun korrekt, ob bereits ein vollständiges Backup vorhanden ist und schlägt in diesem Fall automatisch ein inkrementelles Backup vor. Dies funktioniert jetzt auch für verschlüsselte Backups.
681 lines
28 KiB
Python
681 lines
28 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)
|
|
elif backup_type == "incremental":
|
|
self.app.vollbackup_var.set(False)
|
|
self.app.inkrementell_var.set(True)
|
|
|
|
def _update_backup_type_controls(self):
|
|
if self.app.mode != 'backup' or self.app.left_canvas_data.get('folder') != "Computer":
|
|
return
|
|
|
|
if self.app.full_backup_cb.cget('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")
|
|
encrypted_container = os.path.join(pybackup_dir, "pybackup_encrypted.luks")
|
|
|
|
if os.path.exists(encrypted_container):
|
|
# If an encrypted container exists, we check for any directory inside the pybackup folder.
|
|
# The presence of any item other than the .luks file and its .txt info file suggests a backup has been made.
|
|
if os.path.isdir(pybackup_dir):
|
|
for item in os.listdir(pybackup_dir):
|
|
if not item.endswith(('.luks', '.txt')) and os.path.isdir(os.path.join(pybackup_dir, item)):
|
|
full_backup_exists = True
|
|
break
|
|
else:
|
|
# For non-encrypted backups, check for a directory that represents a full backup.
|
|
system_backups = self.app.backup_manager.list_system_backups(pybackup_dir)
|
|
for backup in system_backups:
|
|
if backup.get('backup_type_base') == 'Full':
|
|
full_backup_exists = True
|
|
break
|
|
|
|
if full_backup_exists:
|
|
self._set_backup_type("incremental")
|
|
else:
|
|
self._set_backup_type("full")
|
|
|
|
def _refresh_backup_options_ui(self):
|
|
# Reset enabled/disabled state for all potentially affected controls
|
|
self.app.full_backup_cb.config(state="normal")
|
|
self.app.incremental_cb.config(state="normal")
|
|
self.app.compressed_cb.config(state="normal")
|
|
self.app.encrypted_cb.config(state="normal")
|
|
self.app.accurate_size_cb.config(state="normal")
|
|
|
|
# Apply logic: Encryption and Compression are mutually exclusive
|
|
if self.app.encrypted_var.get():
|
|
self.app.compressed_var.set(False)
|
|
self.app.compressed_cb.config(state="disabled")
|
|
|
|
if self.app.compressed_var.get():
|
|
self.app.encrypted_var.set(False)
|
|
self.app.encrypted_cb.config(state="disabled")
|
|
# Compression forces 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")
|
|
self.app.accurate_size_cb.config(state="disabled")
|
|
self.app.genaue_berechnung_var.set(False)
|
|
|
|
# After setting the states, determine the final full/incremental choice
|
|
self._update_backup_type_controls()
|
|
|
|
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):
|
|
self._refresh_backup_options_ui()
|
|
|
|
def handle_encryption_change(self):
|
|
self._refresh_backup_options_ui()
|
|
|
|
def on_toggle_accurate_size_calc(self):
|
|
if not self.app.genaue_berechnung_var.get():
|
|
return
|
|
|
|
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.")
|
|
|
|
app_logger.log("Accurate incremental size calculation requested.")
|
|
self.app.accurate_calculation_running = True
|
|
self._set_ui_state(False, keep_cancel_enabled=True)
|
|
self.app.start_pause_button.config(text=Msg.STR["cancel_backup"])
|
|
|
|
self.app.drawing.reset_projection_canvases()
|
|
|
|
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()
|
|
|
|
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)
|
|
self.app.genaue_berechnung_var.set(False)
|
|
self.app.accurate_calculation_running = False
|
|
self.app.animated_icon.stop("DISABLE")
|
|
return
|
|
|
|
def threaded_incremental_calc():
|
|
status = '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
|
|
)
|
|
status = 'success' if size > 0 else 'failure'
|
|
except Exception as e:
|
|
app_logger.log(f"Error during threaded_incremental_calc: {e}")
|
|
status = 'failure'
|
|
finally:
|
|
if self.app.accurate_calculation_running:
|
|
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_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']
|
|
|
|
extra_info = ""
|
|
if button_text == "Computer":
|
|
if self.app.mode == 'backup':
|
|
extra_info = Msg.STR["system_backup_info"]
|
|
else:
|
|
extra_info = Msg.STR["system_restore_info"]
|
|
else:
|
|
if self.app.mode == 'backup':
|
|
extra_info = Msg.STR["user_backup_info"]
|
|
else:
|
|
extra_info = Msg.STR["user_restore_info"]
|
|
|
|
if self.app.mode == 'backup':
|
|
self._update_backup_type_controls()
|
|
else:
|
|
self.app.config_manager.set_setting(
|
|
"restore_destination_path", folder_path)
|
|
|
|
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()
|
|
|
|
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:
|
|
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:
|
|
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.header_frame.refresh_status() # Refresh keyring status
|
|
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)
|
|
|
|
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")
|
|
|
|
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")
|
|
|
|
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:
|
|
if not size_str or size_str == Msg.STR["calculating_size"]:
|
|
return 0
|
|
|
|
parts = size_str.split()
|
|
if len(parts) != 2:
|
|
return 0
|
|
|
|
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):
|
|
for text, data in self.app.buttons_map.items():
|
|
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
|
|
|
|
self.app.schedule_dialog_button.config(
|
|
state="normal" if enable else "disabled")
|
|
self.app.settings_button.config(
|
|
state="normal" if enable else "disabled")
|
|
|
|
self.app.mode_button.config(state="normal" if enable else "disabled")
|
|
|
|
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")
|
|
|
|
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="")
|
|
|
|
if enable:
|
|
self.app.update_backup_options_from_config()
|
|
self.app.actions._update_backup_type_controls()
|
|
else:
|
|
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")
|
|
|
|
if keep_cancel_enabled:
|
|
self.app.start_pause_button.config(state="normal")
|
|
|
|
def toggle_start_cancel(self):
|
|
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)
|
|
|
|
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()
|
|
|
|
self.app.task_progress.stop()
|
|
self.app.task_progress.config(mode="determinate", value=0)
|
|
self.app.info_label.config(
|
|
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 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
|
|
|
|
self.app.backup_is_running = False
|
|
self.app.start_pause_button["text"] = Msg.STR["start"]
|
|
self._set_ui_state(True)
|
|
|
|
else:
|
|
if self.app.start_pause_button['state'] == 'disabled':
|
|
return
|
|
|
|
self.app.backup_is_running = True
|
|
|
|
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...")
|
|
self.app._update_duration()
|
|
|
|
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
|
|
|
|
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}"
|
|
final_dest = os.path.join(base_dest, "pybackup", folder_name)
|
|
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,
|
|
is_encrypted=is_encrypted,
|
|
mode=mode,
|
|
password=password)
|
|
|
|
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) |