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 logging import getLogger
import ofunctions.logger_utils
@ -6,9 +20,6 @@ import ofunctions.logger_utils
logger = getLogger()
EXIT_CODE = 0
def execution_logs(start_time: datetime) -> None:
"""
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
Makes sense ;)
"""
global EXIT_CODE
end_time = datetime.utcnow()
@ -33,7 +43,6 @@ def execution_logs(start_time: datetime) -> None:
logger_worst_level = flt.worst_level
log_level_reached = "success"
EXIT_CODE = logger_worst_level
try:
if logger_worst_level >= 40:
log_level_reached = "errors"
@ -46,4 +55,5 @@ def execution_logs(start_time: datetime) -> None:
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
def group_runner(
self, operations_config: dict, result_queue: Optional[queue.Queue]
self, repo_list: list, operation: str, result_queue: Optional[queue.Queue]
) -> bool:
print(operations_config)
for repo in repo_list:
print(f"Running {operation} for repo {repo}")
print("run to the hills")

View file

@ -14,7 +14,7 @@ from typing import List, Optional, Tuple
import sys
import os
from pathlib import Path
from logging import getLogger
import ofunctions.logger_utils
from datetime import datetime
import dateutil
import queue
@ -57,10 +57,15 @@ from npbackup.customization import (
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.SetOptions(icon=OEM_ICON)
logger = getLogger()
# 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
@ -199,21 +204,21 @@ def _gui_update_state(
window, current_state: bool, backup_tz: Optional[datetime], snapshot_list: List[str]
) -> None:
if current_state:
window["state-button"].Update(
window["--STATE-BUTTON--"].Update(
"{}: {}".format(_t("generic.up_to_date"), backup_tz),
button_color=GUI_STATE_OK_BUTTON,
)
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
)
elif current_state is False:
window["state-button"].Update(
window["--STATE-BUTTON--"].Update(
"{}: {}".format(_t("generic.too_old"), backup_tz.replace(microsecond=0)),
button_color=GUI_STATE_OLD_BUTTON,
)
elif current_state is None:
window["state-button"].Update(
window["--STATE-BUTTON--"].Update(
_t("generic.not_connected_yet"), button_color=GUI_STATE_UNKNOWN_BUTTON
)
@ -619,7 +624,7 @@ def _main_gui():
[
sg.Button(
_t("generic.unknown"),
key="state-button",
key="--STATE-BUTTON--",
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.see_content"), key="see-content"),
sg.Button(_t("generic.forget"), key="forget"),
sg.Button(_t("main_gui.operations"), key="operations"),
sg.Button(_t("generic.configure"), key="configure"),
sg.Button(_t("generic.about"), key="about"),
sg.Button(_t("generic.quit"), key="-EXIT-"),
sg.Button(_t("main_gui.launch_backup"), key="--LAUNCH-BACKUP--"),
sg.Button(_t("main_gui.see_content"), key="--SEE-CONTENT--"),
sg.Button(_t("generic.forget"), key="--FORGET--"),
sg.Button(_t("main_gui.operations"), key="--OPERATIONS--"),
sg.Button(_t("generic.configure"), key="--CONFIGURE--"),
sg.Button(_t("generic.about"), key="--ABOUT--"),
sg.Button(_t("generic.quit"), key="--EXIT--"),
],
],
element_justification="C",
@ -694,7 +699,7 @@ def _main_gui():
while True:
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
if event == "-active_repo-":
active_repo = values["-active_repo-"]
@ -708,7 +713,7 @@ def _main_gui():
else:
sg.PopupError("Repo not existent in config")
continue
if event == "launch-backup":
if event == "--LAUNCH-BACKUP--":
progress_windows_layout = [
[
sg.Multiline(
@ -762,17 +767,16 @@ def _main_gui():
)
progress_window.close()
continue
if event == "see-content":
if event == "--SEE-CONTENT--":
if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
continue
print(values["snapshot-list"])
if len(values["snapshot-list"]) > 1:
sg.Popup(_t("main_gui.select_only_one_snapshot"))
continue
snapshot_to_see = snapshot_list[values["snapshot-list"][0]][0]
ls_window(repo_config, snapshot_to_see)
if event == "forget":
if event == "--FORGET--":
if not values["snapshot-list"]:
sg.Popup(_t("main_gui.select_backup"), keep_on_top=True)
continue
@ -781,14 +785,14 @@ def _main_gui():
snapshots_to_forget.append(snapshot_list[row][0])
forget_snapshot(repo_config, snapshots_to_forget)
# Make sure we trigger a GUI refresh after forgetting snapshots
event = "state-button"
if event == "operations":
full_config = operations_gui(full_config, config_file)
event = "state-button"
if event == "configure":
event = "--STATE-BUTTON--"
if event == "--OPERATIONS--":
full_config = operations_gui(full_config)
event = "--STATE-BUTTON--"
if event == "--CONFIGURE--":
full_config = config_gui(full_config, config_file)
# Make sure we trigger a GUI refresh when configuration is changed
event = "state-button"
event = "--STATE-BUTTON--"
if event == _t("generic.destination"):
try:
if backend_type:
@ -799,9 +803,9 @@ def _main_gui():
sg.PopupNoFrame(destination_string)
except (TypeError, KeyError):
sg.PopupNoFrame(_t("main_gui.unknown_repo"))
if event == "about":
if event == "--ABOUT--":
_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)
_gui_update_state(window, current_state, backup_tz, snapshot_list)
if current_state is None:
@ -815,7 +819,7 @@ def main_gui():
)
try:
_main_gui()
sys.exit(npbackup.common.EXIT_CODE)
sys.exit(logger.get_worst_logger_level())
except _tkinter.TclError as exc:
logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?')
sys.exit(250)

View file

@ -38,34 +38,28 @@ from npbackup.customization import (
logger = getLogger(__intname__)
def add_repo(config_dict: dict) -> dict:
pass
def gui_update_state(window, config_dict: dict) -> list:
def gui_update_state(window, full_config: dict) -> list:
repo_list = []
try:
for repo_name in config_dict["repos"]:
if (
config_dict["repos"][repo_name]["repository"]
and config_dict["repos"][repo_name]["password"]
for repo_name in full_config.g("repos"):
repo_config, _ = configuration.get_repo_config(full_config, repo_name)
if repo_config.g(f"repo_uri") and (
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(
config_dict["repos"][repo_name]["repository"]
repo_config.g(f"repo_uri")
)
repo_list.append([backend_type, repo_uri])
else:
logger.warning("Incomplete operations repo {}".format(repo_name))
except KeyError:
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)
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
"""
@ -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.edit_repo"), key="edit-repo"),
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(_t("operations_gui.quick_check"), key="--QUICK-CHECK--"),
sg.Button(_t("operations_gui.full_check"), key="--FULL-CHECK--"),
],
[
sg.Button(
@ -118,11 +107,11 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
],
[
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",
)
@ -144,7 +133,7 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
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
window["repo-list"].expand(True, True)
@ -152,34 +141,14 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
while True:
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
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 [
"forget",
"quick-check",
"full-check",
"standard-prune",
"max-prune",
"--FORGET--",
"--QUICK-CHECK--",
"--FULL-CHECK--",
"--STANDARD-PRUNE--",
"--MAX-PRUNE--",
]:
if not values["repo-list"]:
result = sg.popup(
@ -188,14 +157,29 @@ def operations_gui(config_dict: dict, config_file: str) -> dict:
)
if not result == _t("generic.yes"):
continue
repos = full_repo_list
repos = complete_repo_list
else:
repos = values["repo-list"]
result_queue = queue.Queue()
runner = NPBackupRunner()
runner.group_runner(repos, result_queue)
if event == "state-update":
full_repo_list = gui_update_state(window, config_dict)
print(repos)
group_runner_repo_list = [repo_name for backend_type, repo_name in repos]
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()
return config_dict
return full_config

View file

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

View file

@ -14,7 +14,7 @@ fr:
only_include: Inclure seulement
destination_folder: Dossier de destination
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
external_server: serveur externalisé
launch_backup: Sauvegarder