Corrected the parenting of BackupContentFrame in main_app.py to ensure it's a child of content_frame, aligning its layout behavior with SettingsFrame. Resolved AttributeError in BackupContentFrame by updating style lookup to use the top-level window's style object, ensuring correct background color retrieval.
719 lines
32 KiB
Python
719 lines
32 KiB
Python
#!/usr/bin/python3
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
import os
|
|
import datetime
|
|
from queue import Queue, Empty
|
|
|
|
from shared_libs.log_window import LogWindow
|
|
from shared_libs.logger import app_logger
|
|
from shared_libs.animated_icon import AnimatedIcon
|
|
from shared_libs.common_tools import IconManager
|
|
from shared_libs.config_manager import ConfigManager
|
|
from backup_manager import BackupManager
|
|
from pbp_app_config import AppConfig, Msg
|
|
from pyimage_ui.scheduler_frame import SchedulerFrame
|
|
from pyimage_ui.backup_content_frame import BackupContentFrame
|
|
from pyimage_ui.header_frame import HeaderFrame
|
|
from pyimage_ui.settings_frame import SettingsFrame
|
|
from core.data_processing import DataProcessing
|
|
from pyimage_ui.drawing import Drawing
|
|
from pyimage_ui.navigation import Navigation
|
|
from pyimage_ui.actions import Actions
|
|
|
|
|
|
class MainApplication(tk.Tk):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
AppConfig.ensure_directories()
|
|
|
|
self.title(AppConfig.UI_CONFIG["window_title"])
|
|
self.geometry("1000x800")
|
|
|
|
self.style = ttk.Style()
|
|
self.tk.call("source", "/usr/share/TK-Themes/water.tcl")
|
|
self.tk.call("set_theme", "light")
|
|
self.style.configure("Custom.TFrame", background="#2b3e4f")
|
|
self.style.configure("Sidebar.TButton", background="#2b3e4f",
|
|
foreground="white", font=("Ubuntu", 12, "bold"))
|
|
self.style.layout("Sidebar.TButton", self.style.layout(
|
|
"SidebarHover.TButton.Borderless.Round"))
|
|
|
|
self.style.map("Toolbutton", background=[
|
|
("active", "#000000")], foreground=[("active", "black")])
|
|
|
|
self.style.layout("Gray.Toolbutton",
|
|
self.style.layout("Toolbutton"))
|
|
self.style.configure("Gray.Toolbutton", foreground="gray")
|
|
|
|
main_frame = ttk.Frame(self)
|
|
main_frame.grid(row=0, column=0, sticky="nsew")
|
|
self.grid_rowconfigure(0, weight=1)
|
|
self.grid_columnconfigure(0, weight=1)
|
|
|
|
sidebar = ttk.Frame(main_frame, style="Custom.TFrame")
|
|
sidebar.grid(row=0, column=0, sticky="nsew")
|
|
main_frame.grid_rowconfigure(0, weight=1)
|
|
main_frame.grid_columnconfigure(0, weight=0, minsize=180)
|
|
main_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
self.content_frame = ttk.Frame(main_frame)
|
|
self.content_frame.grid(row=0, column=1, sticky="nsew")
|
|
self.content_frame.grid_rowconfigure(0, weight=0)
|
|
self.content_frame.grid_rowconfigure(1, weight=0)
|
|
self.content_frame.grid_rowconfigure(2, weight=1)
|
|
self.content_frame.grid_rowconfigure(3, weight=0)
|
|
self.content_frame.grid_rowconfigure(4, weight=0)
|
|
self.content_frame.grid_rowconfigure(5, weight=0)
|
|
self.content_frame.grid_rowconfigure(6, weight=0)
|
|
self.content_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.backup_manager = BackupManager(app_logger)
|
|
self.queue = Queue()
|
|
self.image_manager = IconManager()
|
|
|
|
self.config_manager = ConfigManager(AppConfig.SETTINGS_FILE)
|
|
self.data_processing = DataProcessing(self)
|
|
self.drawing = Drawing(self)
|
|
self.navigation = Navigation(self)
|
|
self.actions = Actions(self)
|
|
|
|
self.backup_is_running = False
|
|
self.start_time = None
|
|
|
|
self.calculation_thread = None
|
|
self.calculation_stop_event = None
|
|
self.source_larger_than_partition = False
|
|
self.accurate_calculation_running = False
|
|
self.is_first_backup = False
|
|
|
|
self.left_canvas_animation = None
|
|
self.right_canvas_animation = None
|
|
self.right_calculation_thread = None
|
|
self.right_calculation_stop_event = None
|
|
self.destination_path = None
|
|
self.source_size_bytes = 0
|
|
self.destination_used_bytes = 0
|
|
self.destination_total_bytes = 0
|
|
|
|
self.backup_left_canvas_data = {}
|
|
self.backup_right_canvas_data = {}
|
|
self.restore_left_canvas_data = {}
|
|
self.restore_right_canvas_data = {}
|
|
|
|
self.left_canvas_data = {}
|
|
self.right_canvas_data = {}
|
|
self.restore_destination_folder_size_bytes = 0
|
|
self.restore_source_size_bytes = 0
|
|
try:
|
|
lx_backup_icon = self.image_manager.get_icon('lx_backup_large')
|
|
if lx_backup_icon:
|
|
self.iconphoto(True, lx_backup_icon)
|
|
except Exception:
|
|
pass
|
|
|
|
lx_backup_label = ttk.Label(
|
|
sidebar, image=lx_backup_icon, background="#2b3e4f")
|
|
lx_backup_label.image = lx_backup_icon
|
|
lx_backup_label.pack(pady=10)
|
|
|
|
self.sidebar_buttons_frame = ttk.Frame(sidebar, style="Custom.TFrame")
|
|
self.sidebar_buttons_frame.pack(pady=20)
|
|
|
|
self.buttons_map = {
|
|
"Computer": {"icon": "computer_extralarge"},
|
|
"Documents": {"icon": "documents_extralarge"},
|
|
"Pictures": {"icon": "pictures_extralarge"},
|
|
"Music": {"icon": "music_extralarge"},
|
|
"Videos": {"icon": "video_extralarge_folder"},
|
|
}
|
|
|
|
for text, data in self.buttons_map.items():
|
|
button = ttk.Button(self.sidebar_buttons_frame, text=text, style="Sidebar.TButton",
|
|
command=lambda t=text: self.actions.on_sidebar_button_click(t))
|
|
button.pack(fill=tk.X, pady=10)
|
|
|
|
self.schedule_dialog_button = ttk.Button(
|
|
self.sidebar_buttons_frame, text=Msg.STR["scheduling"], command=lambda: self.navigation.toggle_scheduler_frame(3), style="Sidebar.TButton")
|
|
self.schedule_dialog_button.pack(fill=tk.X, pady=10)
|
|
|
|
self.settings_button = ttk.Button(
|
|
self.sidebar_buttons_frame, text=Msg.STR["settings"], command=lambda: self.navigation.toggle_settings_frame(4), style="Sidebar.TButton")
|
|
self.settings_button.pack(fill=tk.X, pady=10)
|
|
|
|
self.header_frame = HeaderFrame(self.content_frame, self.image_manager)
|
|
self.header_frame.grid(row=0, column=0, sticky="nsew")
|
|
|
|
self.top_bar = ttk.Frame(self.content_frame)
|
|
self.top_bar.grid(row=1, column=0, sticky="ew", pady=10)
|
|
|
|
top_nav_frame = ttk.Frame(self.top_bar)
|
|
top_nav_frame.pack(side=tk.LEFT)
|
|
|
|
self.nav_buttons_defs = [
|
|
(Msg.STR["backup_menu"], lambda: self.navigation.toggle_mode(
|
|
"backup", 0, trigger_calculation=True)),
|
|
(Msg.STR["restore"], lambda: self.navigation.toggle_mode(
|
|
"restore", 1, trigger_calculation=True)),
|
|
(Msg.STR["backup_content"],
|
|
lambda: self.navigation.toggle_backup_content_frame(2)),
|
|
(Msg.STR["scheduling"],
|
|
lambda: self.navigation.toggle_scheduler_frame(3)),
|
|
(Msg.STR["settings"],
|
|
lambda: self.navigation.toggle_settings_frame(4)),
|
|
(Msg.STR["log"], lambda: self.navigation.toggle_log_window(5)),
|
|
]
|
|
|
|
self.nav_buttons = []
|
|
self.nav_progress_bars = []
|
|
|
|
for i, (text, command) in enumerate(self.nav_buttons_defs):
|
|
button_frame = ttk.Frame(top_nav_frame)
|
|
button_frame.pack(side=tk.LEFT, padx=5)
|
|
button = ttk.Button(button_frame, text=text,
|
|
command=command, style="TButton.Borderless.Round")
|
|
button.pack(side=tk.TOP)
|
|
self.nav_buttons.append(button)
|
|
progress_bar = ttk.Progressbar(
|
|
button_frame, orient="horizontal", length=50, mode="determinate", style="Small.Horizontal.TProgressbar")
|
|
progress_bar.pack_forget()
|
|
self.nav_progress_bars.append(progress_bar)
|
|
|
|
if i < len(self.nav_buttons_defs) - 1:
|
|
ttk.Separator(top_nav_frame, orient=tk.VERTICAL).pack(
|
|
side=tk.LEFT, fill=tk.Y, padx=2)
|
|
|
|
self.canvas_frame = ttk.Frame(self.content_frame)
|
|
self.canvas_frame.grid(
|
|
row=2, column=0, sticky="nsew", padx=10, pady=(15, 5))
|
|
self.canvas_frame.grid_columnconfigure(0, weight=1)
|
|
self.canvas_frame.grid_columnconfigure(2, weight=1)
|
|
self.canvas_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
self.left_canvas = tk.Canvas(
|
|
self.canvas_frame, relief="solid", borderwidth=1)
|
|
self.left_canvas.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
|
self.left_canvas.bind("<Configure>", self.drawing.redraw_left_canvas)
|
|
|
|
button_frame = ttk.Frame(self.canvas_frame)
|
|
button_frame.grid(row=0, column=1, padx=15)
|
|
|
|
self.mode_button_icon = self.image_manager.get_icon(
|
|
"forward_extralarge")
|
|
self.mode_button = ttk.Button(
|
|
button_frame, image=self.mode_button_icon, command=self.navigation.toggle_mode, style="TButton.Borderless.Round")
|
|
self.mode_button.pack(pady=30)
|
|
|
|
self.right_canvas = tk.Canvas(
|
|
self.canvas_frame, relief="solid", borderwidth=1)
|
|
self.right_canvas.grid(row=0, column=2, sticky="nsew", padx=5, pady=5)
|
|
self.right_canvas.bind("<Configure>", self.drawing.redraw_right_canvas)
|
|
self.right_canvas.bind(
|
|
"<Button-1>", self.actions.on_right_canvas_click)
|
|
|
|
self._setup_log_window()
|
|
self._setup_scheduler_frame()
|
|
self._setup_settings_frame()
|
|
self._setup_backup_content_frame()
|
|
|
|
self._setup_task_bar()
|
|
|
|
self.source_size_frame = ttk.LabelFrame(
|
|
self.content_frame, text=Msg.STR["source"], padding=10)
|
|
self.source_size_frame.grid(
|
|
row=4, column=0, sticky="ew", padx=10, pady=5)
|
|
self.source_size_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.source_size_canvas = tk.Canvas(
|
|
self.source_size_frame, height=20, relief="solid", borderwidth=1)
|
|
self.source_size_canvas.grid(row=0, column=0, sticky="ew")
|
|
|
|
source_label_frame = ttk.Frame(self.source_size_frame)
|
|
source_label_frame.grid(row=1, column=0, sticky="ew")
|
|
self.source_size_label = ttk.Label(
|
|
source_label_frame, text="0.00 GB / 0.00 GB")
|
|
self.source_size_label.pack(side=tk.RIGHT)
|
|
|
|
self.target_size_frame = ttk.LabelFrame(
|
|
self.content_frame, text=Msg.STR["projected_usage_label"], padding=10)
|
|
self.target_size_frame.grid(
|
|
row=5, column=0, sticky="ew", padx=10, pady=5)
|
|
self.target_size_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.target_size_canvas = tk.Canvas(
|
|
self.target_size_frame, height=20, relief="solid", borderwidth=1)
|
|
self.target_size_canvas.grid(row=0, column=0, sticky="ew")
|
|
|
|
target_label_frame = ttk.Frame(self.target_size_frame)
|
|
target_label_frame.grid(row=1, column=0, sticky="ew")
|
|
self.target_size_label = ttk.Label(
|
|
target_label_frame, text="0.00 GB / 0.00 GB")
|
|
self.target_size_label.pack(side=tk.RIGHT)
|
|
|
|
self.restore_size_frame_before = ttk.LabelFrame(
|
|
self.content_frame, text="Before Restoration", padding=10)
|
|
self.restore_size_frame_before.grid(
|
|
row=4, column=0, sticky="ew", padx=10, pady=5)
|
|
self.restore_size_frame_before.grid_columnconfigure(0, weight=1)
|
|
|
|
self.restore_size_canvas_before = tk.Canvas(
|
|
self.restore_size_frame_before, height=20, relief="solid", borderwidth=1)
|
|
self.restore_size_canvas_before.grid(row=0, column=0, sticky="ew")
|
|
|
|
restore_label_frame_before = ttk.Frame(self.restore_size_frame_before)
|
|
restore_label_frame_before.grid(row=1, column=0, sticky="ew")
|
|
self.restore_size_label_before = ttk.Label(
|
|
restore_label_frame_before, text="0.00 GB / 0.00 GB")
|
|
self.restore_size_label_before.pack(side=tk.RIGHT)
|
|
|
|
self.restore_size_frame_after = ttk.LabelFrame(
|
|
self.content_frame, text="After Restoration", padding=10)
|
|
self.restore_size_frame_after.grid(
|
|
row=5, column=0, sticky="ew", padx=10, pady=5)
|
|
self.restore_size_frame_after.grid_columnconfigure(0, weight=1)
|
|
|
|
self.restore_size_canvas_after = tk.Canvas(
|
|
self.restore_size_frame_after, height=20, relief="solid", borderwidth=1)
|
|
self.restore_size_canvas_after.grid(row=0, column=0, sticky="ew")
|
|
|
|
restore_label_frame_after = ttk.Frame(self.restore_size_frame_after)
|
|
restore_label_frame_after.grid(row=1, column=0, sticky="ew")
|
|
|
|
self.restore_size_label_after = ttk.Label(
|
|
restore_label_frame_after, text="0.00 GB / 0.00 GB")
|
|
self.restore_size_label_after.pack(side=tk.RIGHT)
|
|
|
|
self.restore_size_label_diff = ttk.Label(
|
|
restore_label_frame_after, text="")
|
|
self.restore_size_label_diff.pack(side=tk.RIGHT)
|
|
|
|
self.restore_size_frame_before.grid_remove()
|
|
self.restore_size_frame_after.grid_remove()
|
|
|
|
self._load_state_and_initialize()
|
|
self.update_backup_options_from_config() # Add this call
|
|
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
|
|
|
def _load_state_and_initialize(self):
|
|
"""Loads saved state from config and initializes the UI."""
|
|
last_mode = self.config_manager.get_setting("last_mode", "backup")
|
|
|
|
# Pre-load data from config before initializing the UI
|
|
backup_source_path = self.config_manager.get_setting(
|
|
"backup_source_path")
|
|
if backup_source_path and os.path.isdir(backup_source_path):
|
|
folder_name = next((name for name, path_obj in AppConfig.FOLDER_PATHS.items(
|
|
) if str(path_obj) == backup_source_path), None)
|
|
|
|
if folder_name:
|
|
icon_name = self.buttons_map[folder_name]['icon']
|
|
else:
|
|
# Handle custom folder path
|
|
folder_name = os.path.basename(backup_source_path.rstrip('/'))
|
|
icon_name = 'folder_extralarge' # A generic folder icon
|
|
|
|
self.backup_left_canvas_data.update({
|
|
'icon': icon_name,
|
|
'folder': folder_name,
|
|
'path_display': backup_source_path,
|
|
})
|
|
|
|
backup_dest_path = self.config_manager.get_setting(
|
|
"backup_destination_path")
|
|
if backup_dest_path and os.path.isdir(backup_dest_path):
|
|
self.destination_path = backup_dest_path # Still needed for some logic
|
|
total, used, free = shutil.disk_usage(backup_dest_path)
|
|
self.backup_right_canvas_data.update({
|
|
'folder': os.path.basename(backup_dest_path.rstrip('/')),
|
|
'path_display': backup_dest_path,
|
|
'size': f"{used / (1024**3):.2f} GB / {total / (1024**3):.2f} GB"
|
|
})
|
|
self.destination_total_bytes = total
|
|
self.destination_used_bytes = used
|
|
|
|
restore_src_path = self.config_manager.get_setting(
|
|
"restore_source_path")
|
|
if restore_src_path and os.path.isdir(restore_src_path):
|
|
self.restore_right_canvas_data.update({
|
|
'folder': os.path.basename(restore_src_path.rstrip('/')),
|
|
'path_display': restore_src_path,
|
|
})
|
|
|
|
restore_dest_path = self.config_manager.get_setting(
|
|
"restore_destination_path")
|
|
if restore_dest_path and os.path.isdir(restore_dest_path):
|
|
# Find the corresponding button_text for the path
|
|
folder_name = ""
|
|
for name, path_obj in AppConfig.FOLDER_PATHS.items():
|
|
if str(path_obj) == restore_dest_path:
|
|
folder_name = name
|
|
break
|
|
if folder_name:
|
|
self.restore_left_canvas_data.update({
|
|
'icon': self.buttons_map[folder_name]['icon'],
|
|
'folder': folder_name,
|
|
'path_display': restore_dest_path,
|
|
})
|
|
|
|
# Initialize UI for the last active mode
|
|
self.navigation.initialize_ui_for_mode(last_mode)
|
|
|
|
# Trigger calculations if needed
|
|
if last_mode == 'backup':
|
|
self.after(100, self.actions.on_sidebar_button_click,
|
|
self.backup_left_canvas_data.get('folder', 'Computer'))
|
|
elif last_mode == 'restore':
|
|
# Trigger calculation for the right canvas (source) if a path is set
|
|
if restore_src_path:
|
|
self.drawing.calculate_restore_folder_size()
|
|
# Trigger calculation for the left canvas (destination) based on last selection
|
|
restore_dest_folder = self.restore_left_canvas_data.get(
|
|
'folder', 'Computer')
|
|
self.after(100, self.actions.on_sidebar_button_click,
|
|
restore_dest_folder)
|
|
self.data_processing.process_queue()
|
|
|
|
def _setup_log_window(self):
|
|
self.log_frame = ttk.Frame(self.content_frame)
|
|
self.log_window = LogWindow(self.log_frame)
|
|
self.log_window.pack(fill=tk.BOTH, expand=True)
|
|
app_logger.init_logger(self.log_window.log_message)
|
|
self.log_frame.grid(row=2, column=0, sticky="nsew")
|
|
self.log_frame.grid_remove()
|
|
|
|
def _setup_scheduler_frame(self):
|
|
self.scheduler_frame = SchedulerFrame(
|
|
self.content_frame, self.backup_manager, padding=10)
|
|
self.scheduler_frame.grid(row=2, column=0, sticky="nsew")
|
|
self.scheduler_frame.grid_remove()
|
|
|
|
def _setup_settings_frame(self):
|
|
self.settings_frame = SettingsFrame(
|
|
self.content_frame, self.navigation, self.actions, padding=10)
|
|
self.settings_frame.grid(row=2, column=0, sticky="nsew")
|
|
self.settings_frame.grid_remove()
|
|
|
|
def _setup_backup_content_frame(self):
|
|
self.backup_content_frame = BackupContentFrame(
|
|
self.content_frame, self.backup_manager, self.actions, padding=10)
|
|
self.backup_content_frame.grid(row=2, column=0, sticky="nsew")
|
|
self.backup_content_frame.grid_remove()
|
|
|
|
def _setup_task_bar(self):
|
|
# Define all boolean vars at the top to ensure they exist before use.
|
|
self.vollbackup_var = tk.BooleanVar()
|
|
self.inkrementell_var = tk.BooleanVar()
|
|
self.genaue_berechnung_var = tk.BooleanVar()
|
|
self.testlauf_var = tk.BooleanVar()
|
|
self.compressed_var = tk.BooleanVar()
|
|
self.encrypted_var = tk.BooleanVar()
|
|
self.bypass_security_var = tk.BooleanVar()
|
|
|
|
self.info_checkbox_frame = ttk.Frame(self.content_frame, padding=10)
|
|
self.info_checkbox_frame.grid(row=3, column=0, sticky="ew")
|
|
|
|
self.info_label = ttk.Label(
|
|
self.info_checkbox_frame, text=Msg.STR["info_text_placeholder"])
|
|
self.info_label.pack(anchor=tk.W, fill=tk.X, pady=5)
|
|
|
|
# Frame for time info
|
|
self.time_info_frame = ttk.Frame(self.info_checkbox_frame)
|
|
self.time_info_frame.pack(anchor=tk.W, fill=tk.X, pady=5)
|
|
|
|
self.start_time_label = ttk.Label(
|
|
self.time_info_frame, text="Start: --:--:--")
|
|
self.start_time_label.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.end_time_label = ttk.Label(
|
|
self.time_info_frame, text="Ende: --:--:--")
|
|
self.end_time_label.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.duration_label = ttk.Label(
|
|
self.time_info_frame, text="Dauer: --:--:--")
|
|
self.duration_label.pack(side=tk.LEFT, padx=5)
|
|
|
|
# --- Accurate Size Calculation Frame (on the right) ---
|
|
accurate_size_frame = ttk.Frame(self.time_info_frame)
|
|
accurate_size_frame.pack(side=tk.LEFT, padx=20)
|
|
|
|
self.accurate_size_cb = ttk.Checkbutton(accurate_size_frame, text=Msg.STR["accurate_size_cb_label"],
|
|
variable=self.genaue_berechnung_var, command=self.actions.on_toggle_accurate_size_calc)
|
|
self.accurate_size_cb.pack(side=tk.LEFT, padx=5)
|
|
|
|
accurate_size_info_label = ttk.Label(
|
|
accurate_size_frame, text=Msg.STR["accurate_size_info_label"], foreground="gray")
|
|
accurate_size_info_label.pack(side=tk.LEFT)
|
|
|
|
checkbox_frame = ttk.Frame(self.info_checkbox_frame)
|
|
checkbox_frame.pack(fill=tk.X, pady=5)
|
|
|
|
self.full_backup_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["full_backup"],
|
|
variable=self.vollbackup_var, command=lambda: self.actions.handle_backup_type_change('voll'))
|
|
self.full_backup_cb.pack(side=tk.LEFT, padx=5)
|
|
self.incremental_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["incremental"],
|
|
variable=self.inkrementell_var, command=lambda: self.actions.handle_backup_type_change('inkrementell'))
|
|
self.incremental_cb.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.compressed_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["compressed"],
|
|
variable=self.compressed_var, command=self.actions.handle_compression_change)
|
|
self.compressed_cb.pack(side=tk.LEFT, padx=5)
|
|
self.encrypted_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["encrypted"],
|
|
variable=self.encrypted_var)
|
|
self.encrypted_cb.pack(side=tk.LEFT, padx=5)
|
|
self.test_run_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["test_run"],
|
|
variable=self.testlauf_var)
|
|
self.test_run_cb.pack(side=tk.LEFT, padx=5)
|
|
self.bypass_security_cb = ttk.Checkbutton(checkbox_frame, text=Msg.STR["bypass_security"],
|
|
variable=self.bypass_security_var)
|
|
self.bypass_security_cb.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.action_frame = ttk.Frame(self.content_frame, padding=10)
|
|
self.action_frame.grid(row=6, column=0, sticky="ew")
|
|
self.action_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
bg_color = self.style.lookup('TFrame', 'background')
|
|
backup_animation_type = self.config_manager.get_setting(
|
|
"backup_animation_type", "counter_arc")
|
|
initial_animation_type = "blink"
|
|
if backup_animation_type == "line":
|
|
initial_animation_type = "line"
|
|
|
|
self.animated_icon = AnimatedIcon(
|
|
self.action_frame, width=20, height=20, use_pillow=True, bg=bg_color, animation_type=initial_animation_type)
|
|
self.animated_icon.grid(row=0, column=0, rowspan=2, padx=5)
|
|
self.animated_icon.stop("DISABLE")
|
|
self.animated_icon.animation_type = backup_animation_type
|
|
|
|
progress_container = ttk.Frame(self.action_frame)
|
|
progress_container.grid(row=0, column=1, sticky="ew", padx=5)
|
|
progress_container.grid_columnconfigure(0, weight=1)
|
|
|
|
self.current_file_label = ttk.Label(
|
|
progress_container, text="", anchor="w")
|
|
self.current_file_label.grid(row=0, column=0, sticky="ew")
|
|
|
|
self.task_progress = ttk.Progressbar(
|
|
progress_container, orient="horizontal", length=100, mode="determinate")
|
|
self.task_progress.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
|
|
|
self.start_pause_button = ttk.Button(
|
|
self.action_frame, text=Msg.STR["start"], command=self.actions.toggle_start_cancel, state="disabled")
|
|
self.start_pause_button.grid(row=0, column=2, rowspan=2, padx=5)
|
|
|
|
def on_closing(self):
|
|
"""Handles window closing events and saves the app state."""
|
|
self.config_manager.set_setting("last_mode", self.mode)
|
|
|
|
# Save paths from the data dictionaries
|
|
if self.backup_left_canvas_data.get('path_display'):
|
|
self.config_manager.set_setting(
|
|
"backup_source_path", self.backup_left_canvas_data['path_display'])
|
|
else:
|
|
self.config_manager.set_setting("backup_source_path", None)
|
|
|
|
if self.backup_right_canvas_data.get('path_display'):
|
|
self.config_manager.set_setting(
|
|
"backup_destination_path", self.backup_right_canvas_data['path_display'])
|
|
else:
|
|
self.config_manager.set_setting("backup_destination_path", None)
|
|
|
|
if self.restore_left_canvas_data.get('path_display'):
|
|
self.config_manager.set_setting(
|
|
"restore_destination_path", self.restore_left_canvas_data['path_display'])
|
|
else:
|
|
self.config_manager.set_setting("restore_destination_path", None)
|
|
|
|
if self.restore_right_canvas_data.get('path_display'):
|
|
self.config_manager.set_setting(
|
|
"restore_source_path", self.restore_right_canvas_data['path_display'])
|
|
else:
|
|
self.config_manager.set_setting("restore_source_path", None)
|
|
|
|
# Stop any ongoing animations before destroying the application
|
|
if self.left_canvas_animation:
|
|
self.left_canvas_animation.stop()
|
|
self.left_canvas_animation.destroy()
|
|
self.left_canvas_animation = None
|
|
if self.right_canvas_animation:
|
|
self.right_canvas_animation.stop()
|
|
self.right_canvas_animation.destroy()
|
|
self.right_canvas_animation = None
|
|
if self.animated_icon:
|
|
self.animated_icon.stop()
|
|
self.animated_icon.destroy()
|
|
self.animated_icon = None
|
|
|
|
app_logger.log(Msg.STR["app_quit"])
|
|
self.destroy()
|
|
|
|
def _process_backup_queue(self):
|
|
"""Processes messages from the backup thread queue to update the UI safely."""
|
|
try:
|
|
while True:
|
|
message = self.queue.get_nowait()
|
|
|
|
if isinstance(message, tuple) and len(message) == 2:
|
|
message_type, value = message
|
|
|
|
if message_type == 'progress':
|
|
self.task_progress["value"] = value
|
|
self.info_label.config(text=f"Fortschritt: {value}%")
|
|
elif message_type == 'file_update':
|
|
max_len = 120
|
|
if len(value) > max_len:
|
|
value = "..." + value[-max_len:]
|
|
self.current_file_label.config(text=value)
|
|
elif message_type == 'status_update':
|
|
self.info_label.config(text=value)
|
|
elif message_type == 'progress_mode':
|
|
self.task_progress.config(mode=value)
|
|
if value == 'indeterminate':
|
|
self.task_progress.start()
|
|
else:
|
|
self.task_progress.stop()
|
|
elif message_type == 'cancel_button_state':
|
|
self.start_pause_button.config(state=value)
|
|
elif message_type == 'deletion_complete':
|
|
self.backup_content_frame.hide_deletion_status()
|
|
self.backup_content_frame.system_backups_frame._load_backup_content()
|
|
elif message_type == 'error':
|
|
self.animated_icon.stop("DISABLE")
|
|
self.start_pause_button["text"] = "Start"
|
|
self.backup_is_running = False
|
|
elif message_type == 'completion':
|
|
status_info = value
|
|
status = 'error' # Default
|
|
if isinstance(status_info, dict):
|
|
status = status_info.get('status', 'error')
|
|
elif status_info is None: # Fallback for older logic
|
|
status = 'success'
|
|
|
|
if status == 'success':
|
|
self.info_label.config(
|
|
text=Msg.STR["backup_finished_successfully"])
|
|
elif status == 'warning':
|
|
self.info_label.config(
|
|
text=Msg.STR["backup_finished_with_warnings"])
|
|
elif status == 'error':
|
|
self.info_label.config(
|
|
text=Msg.STR["backup_failed"])
|
|
elif status == 'cancelled':
|
|
# This is handled in actions.py, but we clean up here.
|
|
pass
|
|
|
|
self.animated_icon.stop("DISABLE")
|
|
self.start_pause_button["text"] = "Start"
|
|
self.task_progress["value"] = 0
|
|
self.current_file_label.config(text="")
|
|
|
|
if self.start_time:
|
|
end_time = datetime.datetime.now()
|
|
duration = end_time - self.start_time
|
|
total_seconds = int(duration.total_seconds())
|
|
hours, remainder = divmod(total_seconds, 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
duration_str = f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
end_str = end_time.strftime("%H:%M:%S")
|
|
self.end_time_label.config(text=f"Ende: {end_str}")
|
|
self.duration_label.config(
|
|
text=f"Dauer: {duration_str}")
|
|
self.start_time = None
|
|
|
|
self.backup_is_running = False
|
|
self.actions._set_ui_state(True)
|
|
elif message_type == 'completion_accurate':
|
|
if value == 'success':
|
|
self.info_label.config(
|
|
text=Msg.STR["accurate_size_success"])
|
|
else:
|
|
self.info_label.config(
|
|
text=Msg.STR["accurate_size_failed"])
|
|
self.actions._set_ui_state(True)
|
|
else:
|
|
self.queue.put(message)
|
|
break
|
|
|
|
except Empty:
|
|
pass
|
|
|
|
self.after(100, self._process_backup_queue)
|
|
|
|
def quit(self):
|
|
self.on_closing()
|
|
|
|
def update_backup_options_from_config(self):
|
|
"""
|
|
Reads the 'force' settings from the config and updates the main UI checkboxes.
|
|
A 'force' setting overrides the user's selection and disables the control.
|
|
"""
|
|
# Full/Incremental Logic
|
|
force_full = self.config_manager.get_setting(
|
|
"force_full_backup", False)
|
|
force_incremental = self.config_manager.get_setting(
|
|
"force_incremental_backup", False)
|
|
|
|
if force_full:
|
|
self.vollbackup_var.set(True)
|
|
self.inkrementell_var.set(False)
|
|
self.full_backup_cb.config(state="disabled")
|
|
self.incremental_cb.config(state="disabled")
|
|
elif force_incremental:
|
|
self.vollbackup_var.set(False)
|
|
self.inkrementell_var.set(True)
|
|
self.full_backup_cb.config(state="disabled")
|
|
self.incremental_cb.config(state="disabled")
|
|
|
|
# Compression Logic
|
|
force_compression = self.config_manager.get_setting(
|
|
"force_compression", False)
|
|
if force_compression:
|
|
self.compressed_var.set(True)
|
|
self.compressed_cb.config(state="disabled")
|
|
|
|
# Encryption Logic
|
|
force_encryption = self.config_manager.get_setting(
|
|
"force_encryption", False)
|
|
if force_encryption:
|
|
self.encrypted_var.set(True)
|
|
self.encrypted_cb.config(state="disabled")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
import sys
|
|
import shutil
|
|
|
|
parser = argparse.ArgumentParser(description="Py-Backup Application.")
|
|
parser.add_argument(
|
|
"--backup-type", choices=['user', 'system'], help="Type of backup to perform.")
|
|
parser.add_argument(
|
|
"--destination", help="Destination directory for the backup.")
|
|
parser.add_argument("--sources", nargs='+',
|
|
help="List of sources for user backup (e.g., Picture, Documents).")
|
|
parser.add_argument("--mode", choices=['full', 'incremental'],
|
|
default='incremental', help="Mode for system backup.")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.backup_type and args.destination:
|
|
from shared_libs.logger import app_logger as cli_logger
|
|
backup_manager = BackupManager(cli_logger)
|
|
|
|
if args.backup_type == 'user':
|
|
if not args.sources:
|
|
print("Error: --sources are required for user backup.")
|
|
sys.exit(1)
|
|
backup_manager._run_user_backup(
|
|
args.sources, args.destination, None, None)
|
|
|
|
elif args.backup_type == 'system':
|
|
backup_manager._run_system_backup(
|
|
args.destination, args.mode, None, None)
|
|
|
|
sys.exit(0)
|
|
|
|
else:
|
|
app = MainApplication()
|
|
app.mainloop()
|