Files
Py-Backup/main_app.py

789 lines
36 KiB
Python

#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
import os
import datetime
from queue import Queue, Empty
import shutil
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 core.config_manager import ConfigManager
from core.backup_manager import BackupManager
from core.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.backup_manager.encryption_manager, self)
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()
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def _load_state_and_initialize(self):
self.log_window.clear_log()
last_mode = self.config_manager.get_setting("last_mode", "backup")
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:
folder_name = os.path.basename(backup_source_path.rstrip('/'))
icon_name = 'folder_extralarge'
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
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
if hasattr(self, 'header_frame'):
self.header_frame.refresh_status()
container_path = os.path.join(backup_dest_path, "pybackup_encrypted.luks")
if os.path.exists(container_path):
username = os.path.basename(backup_dest_path.rstrip('/'))
password = self.backup_manager.encryption_manager.get_password_from_keyring(username)
if password:
self.backup_manager.encryption_manager.unlock_container(backup_dest_path, password)
app_logger.log("Automatically unlocked encrypted container.")
if hasattr(self, 'header_frame'):
self.header_frame.refresh_status()
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):
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,
})
self.navigation.initialize_ui_for_mode(last_mode)
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':
if restore_src_path:
self.drawing.calculate_restore_folder_size()
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, self, padding=10)
self.backup_content_frame.grid(row=2, column=0, sticky="nsew")
self.backup_content_frame.grid_remove()
def _setup_task_bar(self):
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)
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_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):
if self.destination_path:
# Only try to lock if the container is actually mounted
if self.backup_manager.encryption_manager.is_container_mounted(self.destination_path):
self.backup_manager.encryption_manager.lock_container(self.destination_path)
self.config_manager.set_setting("last_mode", self.mode)
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)
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):
try:
for _ in range(100):
message = self.queue.get_nowait()
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:
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 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="")
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':
pass
else:
app_logger.log(f"Unknown message in queue: {message}")
except Empty:
pass
finally:
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):
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")
force_compression = self.config_manager.get_setting(
"force_compression", False)
if force_compression:
self.compressed_var.set(True)
self.compressed_cb.config(state="disabled")
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
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()