Files
Py-Backup/main_app.py

813 lines
37 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")
self.style.configure("Green.Sidebar.TButton", foreground="green")
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)
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.mode = "backup" # Default mode
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):
self.log_window.clear_log()
"""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._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.duration_label = ttk.Label(
self.time_info_frame, text="Dauer: --:--:--")
self.duration_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)
# --- 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, command=self.actions.handle_encryption_change)
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_queue(self):
"""
Processes all messages from background threads to update the UI safely.
This is the single, consolidated queue processing loop for the entire application.
It processes messages in batches to avoid freezing the UI.
"""
try:
for _ in range(100): # Process up to 100 messages at a time
message = self.queue.get_nowait()
# --- Size Calculation Message Handling (from data_processing) ---
if isinstance(message, tuple) and len(message) in [3, 5]:
calc_type, status = None, None
if len(message) == 5:
button_text, folder_size, mode_when_started, calc_type, status = message
else: # len == 3
button_text, folder_size, mode_when_started = message
if mode_when_started != self.mode:
if calc_type == 'accurate_incremental':
self.actions._set_ui_state(True)
self.genaue_berechnung_var.set(False)
self.accurate_calculation_running = False
self.animated_icon.stop("DISABLE")
else:
current_folder_name = self.left_canvas_data.get('folder')
if current_folder_name == button_text:
if self.left_canvas_animation:
self.left_canvas_animation.stop()
self.left_canvas_animation.destroy()
self.left_canvas_animation = None
size_in_gb = folder_size / (1024**3)
size_str = f"{size_in_gb:.2f} GB" if size_in_gb >= 1 else f"{folder_size / (1024*1024):.2f} MB"
self.left_canvas_data['size'] = size_str
self.left_canvas_data['total_bytes'] = folder_size
self.left_canvas_data['calculating'] = False
self.drawing.redraw_left_canvas()
self.source_size_bytes = folder_size
if self.mode == 'backup':
if button_text in AppConfig.FOLDER_PATHS:
total_disk_size, _, _ = shutil.disk_usage(
AppConfig.FOLDER_PATHS[button_text])
if folder_size > total_disk_size:
self.source_larger_than_partition = True
else:
self.source_larger_than_partition = False
percentage = (
folder_size / total_disk_size) * 100 if total_disk_size > 0 else 0
self.source_size_canvas.delete("all")
fill_width = (
self.source_size_canvas.winfo_width() / 100) * percentage
self.source_size_canvas.create_rectangle(
0, 0, fill_width, self.source_size_canvas.winfo_height(), fill="#0078d7", outline="")
self.source_size_label.config(
text=f"{folder_size / (1024**3):.2f} GB / {total_disk_size / (1024**3):.2f} GB")
self.drawing.update_target_projection()
if self.mode == 'backup' and self.destination_path:
self.start_pause_button.config(state="normal")
if calc_type == 'accurate_incremental':
self.source_size_bytes = folder_size
self.drawing.update_target_projection()
self.animated_icon.stop("DISABLE")
self.task_progress.stop()
self.task_progress.config(mode="determinate", value=0)
self.actions._set_ui_state(True)
self.genaue_berechnung_var.set(False)
self.accurate_calculation_running = False
self.start_pause_button.config(text=Msg.STR["start"])
if status == 'success':
self.info_label.config(
text=Msg.STR["accurate_size_success"], foreground="#0078d7")
self.current_file_label.config(text="")
else:
self.info_label.config(
text=Msg.STR["accurate_size_failed"], foreground="#D32F2F")
self.current_file_label.config(text="")
# --- Backup/Deletion Message Handling (from main_app) ---
elif 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.actions._set_ui_state(True)
self.backup_content_frame.hide_deletion_status()
self.backup_content_frame.system_backups_frame._load_backup_content()
self.backup_content_frame.user_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'
if isinstance(status_info, dict):
status = status_info.get('status', 'error')
elif status_info is None:
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':
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()
end_str = end_time.strftime("%H:%M:%S")
self.end_time_label.config(text=f"Ende: {end_str}")
self.start_time = None
self.backup_is_running = False
self.actions._set_ui_state(True)
self.backup_content_frame.system_backups_frame._load_backup_content()
elif message_type == 'completion_accurate':
# This is now handled by the len=5 case above
pass
else:
app_logger.log(f"Unknown message in queue: {message}")
except Empty:
pass # The queue is empty, do nothing.
finally:
# Always schedule the next check.
self.after(100, self._process_queue)
def _update_duration(self):
if self.backup_is_running and self.start_time:
duration = datetime.datetime.now() - 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}"
self.duration_label.config(text=f"Dauer: {duration_str}")
self.after(1000, self._update_duration)
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")
self.actions._refresh_backup_options_ui()
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()