WIP: Refactor operations GUI

This commit is contained in:
Orsiris de Jong 2023-12-18 21:54:03 +01:00
parent e0f85bacae
commit a0511ce2a5
6 changed files with 93 additions and 94 deletions

View file

@ -1,3 +1,17 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.common"
__author__ = "Orsiris de Jong"
__site__ = "https://www.netperfect.fr/npbackup"
__description__ = "NetPerfect Backup Client"
__copyright__ = "Copyright (C) 2023 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2023121801"
from datetime import datetime from datetime import datetime
from logging import getLogger from logging import getLogger
import ofunctions.logger_utils import ofunctions.logger_utils
@ -6,9 +20,6 @@ import ofunctions.logger_utils
logger = getLogger() logger = getLogger()
EXIT_CODE = 0
def execution_logs(start_time: datetime) -> None: def execution_logs(start_time: datetime) -> None:
""" """
Try to know if logger.warning or worse has been called Try to know if logger.warning or worse has been called
@ -23,7 +34,6 @@ def execution_logs(start_time: datetime) -> None:
So using logger = getLogger("anotherinstance") will create a separate instance from the one we can inspect So using logger = getLogger("anotherinstance") will create a separate instance from the one we can inspect
Makes sense ;) Makes sense ;)
""" """
global EXIT_CODE
end_time = datetime.utcnow() end_time = datetime.utcnow()
@ -33,7 +43,6 @@ def execution_logs(start_time: datetime) -> None:
logger_worst_level = flt.worst_level logger_worst_level = flt.worst_level
log_level_reached = "success" log_level_reached = "success"
EXIT_CODE = logger_worst_level
try: try:
if logger_worst_level >= 40: if logger_worst_level >= 40:
log_level_reached = "errors" log_level_reached = "errors"
@ -46,4 +55,5 @@ def execution_logs(start_time: datetime) -> None:
end_time - start_time, log_level_reached end_time - start_time, log_level_reached
) )
) )
# using sys.exit(code) in a atexit function will swallow the exitcode and render 0 # using sys.exit(code) in a atexit function will swallow the exitcode and render 0
# Using sys.exit(logger.get_worst_logger_level()) is the way to go, when using ofunctions.logger_utils >= 2.4.1

View file

@ -652,7 +652,8 @@ class NPBackupRunner:
return result return result
def group_runner( def group_runner(
self, operations_config: dict, result_queue: Optional[queue.Queue] self, repo_list: list, operation: str, result_queue: Optional[queue.Queue]
) -> bool: ) -> bool:
print(operations_config) for repo in repo_list:
print(f"Running {operation} for repo {repo}")
print("run to the hills") print("run to the hills")

View file

@ -14,7 +14,7 @@ from typing import List, Optional, Tuple
import sys import sys
import os import os
from pathlib import Path from pathlib import Path
from logging import getLogger import ofunctions.logger_utils
from datetime import datetime from datetime import datetime
import dateutil import dateutil
import queue import queue
@ -57,10 +57,15 @@ from npbackup.customization import (
OEM_ICON, OEM_ICON,
) )
LOG_FILE = os.path.join(CURRENT_DIR, "{}.log".format(__intname__))
logger = ofunctions.logger_utils.logger_get_logger(LOG_FILE)
sg.theme(PYSIMPLEGUI_THEME) sg.theme(PYSIMPLEGUI_THEME)
sg.SetOptions(icon=OEM_ICON) sg.SetOptions(icon=OEM_ICON)
logger = getLogger()
# Let's use mutable to get a cheap way of transfering data from thread to main program # Let's use mutable to get a cheap way of transfering data from thread to main program
# There are no possible race conditions since we don't modifiy the data from anywhere outside the thread # There are no possible race conditions since we don't modifiy the data from anywhere outside the thread
@ -199,21 +204,21 @@ def _gui_update_state(
window, current_state: bool, backup_tz: Optional[datetime], snapshot_list: List[str] window, current_state: bool, backup_tz: Optional[datetime], snapshot_list: List[str]
) -> None: ) -> None:
if current_state: if current_state:
window["state-button"].Update( window["--STATE-BUTTON--"].Update(
"{}: {}".format(_t("generic.up_to_date"), backup_tz), "{}: {}".format(_t("generic.up_to_date"), backup_tz),
button_color=GUI_STATE_OK_BUTTON, button_color=GUI_STATE_OK_BUTTON,
) )
elif current_state is False and backup_tz == datetime(1, 1, 1, 0, 0): elif current_state is False and backup_tz == datetime(1, 1, 1, 0, 0):
window["state-button"].Update( window["--STATE-BUTTON--"].Update(
_t("generic.no_snapshots"), button_color=GUI_STATE_OLD_BUTTON _t("generic.no_snapshots"), button_color=GUI_STATE_OLD_BUTTON
) )
elif current_state is False: elif current_state is False:
window["state-button"].Update( window["--STATE-BUTTON--"].Update(
"{}: {}".format(_t("generic.too_old"), backup_tz.replace(microsecond=0)), "{}: {}".format(_t("generic.too_old"), backup_tz.replace(microsecond=0)),
button_color=GUI_STATE_OLD_BUTTON, button_color=GUI_STATE_OLD_BUTTON,
) )
elif current_state is None: elif current_state is None:
window["state-button"].Update( window["--STATE-BUTTON--"].Update(
_t("generic.not_connected_yet"), button_color=GUI_STATE_UNKNOWN_BUTTON _t("generic.not_connected_yet"), button_color=GUI_STATE_UNKNOWN_BUTTON
) )
@ -619,7 +624,7 @@ def _main_gui():
[ [
sg.Button( sg.Button(
_t("generic.unknown"), _t("generic.unknown"),
key="state-button", key="--STATE-BUTTON--",
button_color=("white", "grey"), button_color=("white", "grey"),
) )
], ],
@ -650,13 +655,13 @@ def _main_gui():
) )
], ],
[ [
sg.Button(_t("main_gui.launch_backup"), key="launch-backup"), sg.Button(_t("main_gui.launch_backup"), key="--LAUNCH-BACKUP--"),
sg.Button(_t("main_gui.see_content"), key="see-content"), sg.Button(_t("main_gui.see_content"), key="--SEE-CONTENT--"),
sg.Button(_t("generic.forget"), key="forget"), sg.Button(_t("generic.forget"), key="--FORGET--"),
sg.Button(_t("main_gui.operations"), key="operations"), sg.Button(_t("main_gui.operations"), key="--OPERATIONS--"),
sg.Button(_t("generic.configure"), key="configure"), sg.Button(_t("generic.configure"), key="--CONFIGURE--"),
sg.Button(_t("generic.about"), key="about"), sg.Button(_t("generic.about"), key="--ABOUT--"),
sg.Button(_t("generic.quit"), key="-EXIT-"), sg.Button(_t("generic.quit"), key="--EXIT--"),
], ],
], ],
element_justification="C", element_justification="C",
@ -694,7 +699,7 @@ def _main_gui():
while True: while True:
event, values = window.read(timeout=60000) event, values = window.read(timeout=60000)
if event in (sg.WIN_X_EVENT, sg.WIN_CLOSED, "-EXIT-"): if event in (sg.WIN_X_EVENT, sg.WIN_CLOSED, "--EXIT--"):
break break
if event == "-active_repo-": if event == "-active_repo-":
active_repo = values["-active_repo-"] active_repo = values["-active_repo-"]
@ -708,7 +713,7 @@ def _main_gui():
else: else:
sg.PopupError("Repo not existent in config") sg.PopupError("Repo not existent in config")
continue continue
if event == "launch-backup": if event == "--LAUNCH-BACKUP--":
progress_windows_layout = [ progress_windows_layout = [
[ [
sg.Multiline( sg.Multiline(
@ -762,17 +767,16 @@ def _main_gui():
) )
progress_window.close() progress_window.close()
continue continue
if event == "see-content": if event == "--SEE-CONTENT--":
if not values["snapshot-list"]: if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True) sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
continue continue
print(values["snapshot-list"])
if len(values["snapshot-list"]) > 1: if len(values["snapshot-list"]) > 1:
sg.Popup(_t("main_gui.select_only_one_snapshot")) sg.Popup(_t("main_gui.select_only_one_snapshot"))
continue continue
snapshot_to_see = snapshot_list[values["snapshot-list"][0]][0] snapshot_to_see = snapshot_list[values["snapshot-list"][0]][0]
ls_window(repo_config, snapshot_to_see) ls_window(repo_config, snapshot_to_see)
if event == "forget": if event == "--FORGET--":
if not values["snapshot-list"]: if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True) sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
continue continue
@ -781,14 +785,14 @@ def _main_gui():
snapshots_to_forget.append(snapshot_list[row][0]) snapshots_to_forget.append(snapshot_list[row][0])
forget_snapshot(repo_config, snapshots_to_forget) forget_snapshot(repo_config, snapshots_to_forget)
# Make sure we trigger a GUI refresh after forgetting snapshots # Make sure we trigger a GUI refresh after forgetting snapshots
event = "state-button" event = "--STATE-BUTTON--"
if event == "operations": if event == "--OPERATIONS--":
full_config = operations_gui(full_config, config_file) full_config = operations_gui(full_config)
event = "state-button" event = "--STATE-BUTTON--"
if event == "configure": if event == "--CONFIGURE--":
full_config = config_gui(full_config, config_file) full_config = config_gui(full_config, config_file)
# Make sure we trigger a GUI refresh when configuration is changed # Make sure we trigger a GUI refresh when configuration is changed
event = "state-button" event = "--STATE-BUTTON--"
if event == _t("generic.destination"): if event == _t("generic.destination"):
try: try:
if backend_type: if backend_type:
@ -799,9 +803,9 @@ def _main_gui():
sg.PopupNoFrame(destination_string) sg.PopupNoFrame(destination_string)
except (TypeError, KeyError): except (TypeError, KeyError):
sg.PopupNoFrame(_t("main_gui.unknown_repo")) sg.PopupNoFrame(_t("main_gui.unknown_repo"))
if event == "about": if event == "--ABOUT--":
_about_gui(version_string, full_config) _about_gui(version_string, full_config)
if event == "state-button": if event == "--STATE-BUTTON--":
current_state, backup_tz, snapshot_list = get_gui_data(repo_config) current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
_gui_update_state(window, current_state, backup_tz, snapshot_list) _gui_update_state(window, current_state, backup_tz, snapshot_list)
if current_state is None: if current_state is None:
@ -815,7 +819,7 @@ def main_gui():
) )
try: try:
_main_gui() _main_gui()
sys.exit(npbackup.common.EXIT_CODE) sys.exit(logger.get_worst_logger_level())
except _tkinter.TclError as exc: except _tkinter.TclError as exc:
logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?') logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?')
sys.exit(250) sys.exit(250)

View file

@ -38,34 +38,28 @@ from npbackup.customization import (
logger = getLogger(__intname__) logger = getLogger(__intname__)
def add_repo(config_dict: dict) -> dict: def gui_update_state(window, full_config: dict) -> list:
pass
def gui_update_state(window, config_dict: dict) -> list:
repo_list = [] repo_list = []
try: try:
for repo_name in config_dict["repos"]: for repo_name in full_config.g("repos"):
if ( repo_config, _ = configuration.get_repo_config(full_config, repo_name)
config_dict["repos"][repo_name]["repository"] if repo_config.g(f"repo_uri") and (
and config_dict["repos"][repo_name]["password"] repo_config.g(f"repo_opts.repo_password")
or repo_config.g(f"repo_opts.repo_password_command")
): ):
backend_type, repo_uri = get_anon_repo_uri( backend_type, repo_uri = get_anon_repo_uri(
config_dict["repos"][repo_name]["repository"] repo_config.g(f"repo_uri")
) )
repo_list.append([backend_type, repo_uri]) repo_list.append([backend_type, repo_uri])
else: else:
logger.warning("Incomplete operations repo {}".format(repo_name)) logger.warning("Incomplete operations repo {}".format(repo_name))
except KeyError: except KeyError:
logger.info("No operations repos configured") logger.info("No operations repos configured")
if config_dict["repo"]["repository"] and config_dict["repo"]["password"]:
backend_type, repo_uri = get_anon_repo_uri(config_dict["repo"]["repository"])
repo_list.append("[{}] {}".format(backend_type, repo_uri))
window["repo-list"].update(repo_list) window["repo-list"].update(repo_list)
return repo_list return repo_list
def operations_gui(config_dict: dict, config_file: str) -> dict: def operations_gui(full_config: dict) -> dict:
""" """
Operate on one or multiple repositories Operate on one or multiple repositories
""" """
@ -102,13 +96,8 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
) )
], ],
[ [
sg.Button(_t("operations_gui.add_repo"), key="add-repo"), sg.Button(_t("operations_gui.quick_check"), key="--QUICK-CHECK--"),
sg.Button(_t("operations_gui.edit_repo"), key="edit-repo"), sg.Button(_t("operations_gui.full_check"), key="--FULL-CHECK--"),
sg.Button(_t("operations_gui.remove_repo"), key="remove-repo"),
],
[
sg.Button(_t("operations_gui.quick_check"), key="quick-check"),
sg.Button(_t("operations_gui.full_check"), key="full-check"),
], ],
[ [
sg.Button( sg.Button(
@ -118,11 +107,11 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
], ],
[ [
sg.Button( sg.Button(
_t("operations_gui.standard_prune"), key="standard-prune" _t("operations_gui.standard_prune"), key="--STANDARD-PRUNE--"
), ),
sg.Button(_t("operations_gui.max_prune"), key="max-prune"), sg.Button(_t("operations_gui.max_prune"), key="--MAX-PRUNE--"),
], ],
[sg.Button(_t("generic.quit"), key="exit")], [sg.Button(_t("generic.quit"), key="--EXIT--")],
], ],
element_justification="C", element_justification="C",
) )
@ -144,7 +133,7 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
finalize=True, finalize=True,
) )
full_repo_list = gui_update_state(window, config_dict) complete_repo_list = gui_update_state(window, full_config)
# Auto reisze table to window size # Auto reisze table to window size
window["repo-list"].expand(True, True) window["repo-list"].expand(True, True)
@ -152,34 +141,14 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
while True: while True:
event, values = window.read(timeout=60000) event, values = window.read(timeout=60000)
if event in (sg.WIN_CLOSED, "exit"): if event in (sg.WIN_CLOSED, sg.WIN_X_EVENT, '--EXIT--'):
break break
if event == "add-repo":
pass
if event in ["add-repo", "remove-repo"]:
if not values["repo-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
continue
if event == "add-repo":
config_dict = add_repo(config_dict)
# Save to config here #TODO #WIP
event == "state-update"
elif event == "remove-repo":
result = sg.popup(
_t("generic.are_you_sure"),
custom_text=(_t("generic.yes"), _t("generic.no")),
)
if result == _t("generic.yes"):
# Save to config here #TODO #WIP
event == "state-update"
if event == "forget":
pass
if event in [ if event in [
"forget", "--FORGET--",
"quick-check", "--QUICK-CHECK--",
"full-check", "--FULL-CHECK--",
"standard-prune", "--STANDARD-PRUNE--",
"max-prune", "--MAX-PRUNE--",
]: ]:
if not values["repo-list"]: if not values["repo-list"]:
result = sg.popup( result = sg.popup(
@ -188,14 +157,29 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
) )
if not result == _t("generic.yes"): if not result == _t("generic.yes"):
continue continue
repos = full_repo_list repos = complete_repo_list
else: else:
repos = values["repo-list"] repos = values["repo-list"]
result_queue = queue.Queue() result_queue = queue.Queue()
runner = NPBackupRunner() runner = NPBackupRunner()
runner.group_runner(repos, result_queue) print(repos)
if event == "state-update": group_runner_repo_list = [repo_name for backend_type, repo_name in repos]
full_repo_list = gui_update_state(window, config_dict)
if event == '--FORGET--':
operation = 'forget'
if event == '--QUICK-CHECK--':
operation = 'quick_check'
if event == '--FULL-CHECK--':
operation = 'full_check'
if event == '--STANDARD-PRUNE--':
operation = 'standard_prune'
if event == '--MAX-PRUNE--':
operation = 'max_prune'
runner.group_runner(group_runner_repo_list, operation, result_queue)
event = '---STATE-UPDATE---'
if event == "---STATE-UPDATE---":
complete_repo_list = gui_update_state(window, full_config)
window.close() window.close()
return config_dict return full_config

View file

@ -14,7 +14,7 @@ en:
only_include: Only include only_include: Only include
destination_folder: Destination folder destination_folder: Destination folder
backup_state: Backup state backup_state: Backup state
backup_list_to: List of backups to backup_list_to: List of backups to repo
local_folder: Local folder local_folder: Local folder
external_server: external server external_server: external server
launch_backup: Launch backup launch_backup: Launch backup

View file

@ -14,7 +14,7 @@ fr:
only_include: Inclure seulement only_include: Inclure seulement
destination_folder: Dossier de destination destination_folder: Dossier de destination
backup_state: Etat de sauvegarde backup_state: Etat de sauvegarde
backup_list_to: Liste des sauvegardes vers backup_list_to: Liste des sauvegardes vers le dépot
local_folder: Dossier local local_folder: Dossier local
external_server: serveur externalisé external_server: serveur externalisé
launch_backup: Sauvegarder launch_backup: Sauvegarder