Version 0.1.0, fonctionnelle mais rustique
This commit is contained in:
commit
4d422ada8c
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
config.ini
|
||||||
|
dist/
|
||||||
|
__pycache__
|
||||||
48
Readme.md
Normal file
48
Readme.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
Une interface pour récupérer ses sauvegardes Borgbackup
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
Voici une interface pour accéder facilement à vos sauvegardes BorgBackup
|
||||||
|
|
||||||
|
- Renseignez une fois vos paramètres (ils seront sauvegardés)
|
||||||
|
- Choisissez votre sauvegarde
|
||||||
|
- Parcourez-là avec votre navigateur de fichier habituel
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Toutes les commandes indiquées ci-dessous sont faites pour être exécutées dans le dossier contenant ce `Readme.md`
|
||||||
|
|
||||||
|
Développement
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Ce projet est en Python3 / Qt.
|
||||||
|
|
||||||
|
1. Créez un environnement virtuel python3 `python -m venv .venv`
|
||||||
|
2. Activez-le `source .venv/bin/activate`
|
||||||
|
3. Installez les dépendances `pip install -r requirements`
|
||||||
|
|
||||||
|
Génération du package
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
1. Activez l'environnement de développement comme décrit ci-dessus
|
||||||
|
2. Si vous avez modifié le code, éditer `pyproject.toml`, changez la version.
|
||||||
|
3. Utilisez la commande `python -m build`
|
||||||
|
|
||||||
|
Les fichiers générés se trouve dans le repertoire `dist/`
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
L'installation sur le système se fait avec pipx.
|
||||||
|
|
||||||
|
sudo apt install pipx
|
||||||
|
|
||||||
|
# adaptez le nom à la version générée
|
||||||
|
pipx install borgbackup_gui-0.1.0-py3-none-any.whl
|
||||||
|
|
||||||
|
Intégration au bureau
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
cp borgbackupgui.desktop $HOME/.local/share/applications
|
||||||
|
|
||||||
|
|
||||||
7
borgbackup_gui.desktop
Executable file
7
borgbackup_gui.desktop
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=BorgBackup Gui
|
||||||
|
Comment=An application to view your backups.
|
||||||
|
Exec=borgbackup-gui
|
||||||
|
Icon=
|
||||||
|
Type=Application
|
||||||
|
Categories=Graphics;Utility;
|
||||||
157
borgbackup_gui/BorgBackupGui.py
Normal file
157
borgbackup_gui/BorgBackupGui.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from borgbackup_gui.ConfigRepoGUI import ConfigRepoGUI
|
||||||
|
from borgbackup_gui.ListBackupGUI import ListBackupGUI
|
||||||
|
from PySide6.QtWidgets import QApplication, QWidget,QHBoxLayout, QVBoxLayout, QPlainTextEdit
|
||||||
|
from PySide6.QtCore import QObject, Slot, Signal, QThread
|
||||||
|
import configparser
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class BorgBackupGui(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.config_file = configparser.ConfigParser()
|
||||||
|
self.setWindowTitle("BorgBackup Gui")
|
||||||
|
|
||||||
|
passphrase = ""
|
||||||
|
depot = ""
|
||||||
|
remote_path = "/usr/local/bin/borg"
|
||||||
|
|
||||||
|
if Path("config.ini").exists():
|
||||||
|
self.config_file.read("config.ini")
|
||||||
|
try:
|
||||||
|
passphrase = self.config_file["DEFAULT"]["passphrase"]
|
||||||
|
depot = self.config_file["DEFAULT"]["depot"]
|
||||||
|
remote_path = self.config_file["DEFAULT"]["remote_path"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.backups:list[str] = []
|
||||||
|
self.config = ConfigRepoGUI(
|
||||||
|
passphrase=passphrase,
|
||||||
|
depot=depot,
|
||||||
|
remote_path=remote_path)
|
||||||
|
self.config.valider.connect(self.lister_depot_thread)
|
||||||
|
self.log_console = QPlainTextEdit()
|
||||||
|
self.log_console.setReadOnly(True)
|
||||||
|
self.layout_h = QHBoxLayout()
|
||||||
|
self.layout_v = QVBoxLayout()
|
||||||
|
self.layout_v.addWidget(self.config)
|
||||||
|
self.layout_v.addWidget(self.log_console)
|
||||||
|
self.layout_h.addLayout(self.layout_v)
|
||||||
|
self.setLayout(self.layout_h)
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def lister_depot_thread(self):
|
||||||
|
self.passphrase = self.config.get_passphrase()
|
||||||
|
self.depot = self.config.get_repo()
|
||||||
|
self.remote_path = self.config.get_remote_path()
|
||||||
|
|
||||||
|
# On sauvegarde la configuration dans le fichier de config
|
||||||
|
self.config_file['DEFAULT'] = {
|
||||||
|
'passphrase': self.passphrase,
|
||||||
|
'depot': self.depot,
|
||||||
|
'remote_path': self.remote_path}
|
||||||
|
with open('config.ini', 'w') as configfile:
|
||||||
|
self.config_file.write(configfile)
|
||||||
|
|
||||||
|
self.worker = ListerBackup(passphrase=self.passphrase,
|
||||||
|
depot = self.depot,
|
||||||
|
remote_path = self.remote_path)
|
||||||
|
|
||||||
|
self.worker.finished.connect(self.afficher_liste_depot)
|
||||||
|
self.worker.log_str.connect(self.log_console.appendPlainText)
|
||||||
|
self.worker.start()
|
||||||
|
|
||||||
|
def afficher_liste_depot(self):
|
||||||
|
self.config.task_ended()
|
||||||
|
self.list_backup_gui = ListBackupGUI(self.worker.get_backups())
|
||||||
|
self.list_backup_gui.selected.connect(self.afficher_la_sauvegarde)
|
||||||
|
self.layout_h.addWidget(self.list_backup_gui)
|
||||||
|
self.adjustSize()
|
||||||
|
|
||||||
|
def afficher_la_sauvegarde(self, nom_backup):
|
||||||
|
self.afficheur = MountAndDisplayBackup(self.passphrase, self.depot, self.remote_path, nom_backup)
|
||||||
|
self.afficheur.log_str.connect(self.log_console.appendPlainText)
|
||||||
|
self.afficheur.finished.connect(self.list_backup_gui.activate)
|
||||||
|
self.afficheur.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MountAndDisplayBackup(QThread):
|
||||||
|
log_str = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, passphrase, depot, remote_path, backup):
|
||||||
|
super().__init__()
|
||||||
|
self.passphrase = passphrase
|
||||||
|
self.depot = depot
|
||||||
|
self.remote_path = remote_path
|
||||||
|
self.backup = backup
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# mkdir /tmp/localborg/$(backup)
|
||||||
|
base_dir = "/tmp/localborg/"
|
||||||
|
backup_dir = base_dir + self.backup
|
||||||
|
custom_env = os.environ.copy()
|
||||||
|
custom_env["BORG_PASSPHRASE"] = self.passphrase
|
||||||
|
self.log_str.emit("mkdir -p " + backup_dir)
|
||||||
|
result = subprocess.run(["mkdir", "-p", backup_dir], capture_output=True, text=True)
|
||||||
|
self.log_str.emit(result.stderr)
|
||||||
|
self.log_str.emit(result.stdout)
|
||||||
|
# borg mount nas.local:/volume1/NetBackup/chezmoi/client::2020-04-16 /tmp/localborg
|
||||||
|
self.log_str.emit("borg mount " + self.depot + "::" + self.backup + " " + backup_dir)
|
||||||
|
self.log_str.emit("Veuillez patienter, cette étape peut être longue")
|
||||||
|
result = subprocess.run(["borg", "mount", self.depot + "::" + self.backup, "--remote-path", self.remote_path, backup_dir], env=custom_env, capture_output=True, text=True)
|
||||||
|
self.log_str.emit(result.stderr)
|
||||||
|
self.log_str.emit(result.stdout)
|
||||||
|
self.log_str.emit("xdg-open" + backup_dir)
|
||||||
|
result = subprocess.run(["xdg-open", backup_dir], capture_output=True, text=True)
|
||||||
|
self.log_str.emit(result.stderr)
|
||||||
|
self.log_str.emit(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ListerBackup(QThread):
|
||||||
|
log_str = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, passphrase, depot, remote_path):
|
||||||
|
super().__init__()
|
||||||
|
self.passphrase = passphrase
|
||||||
|
self.depot = depot
|
||||||
|
self.remote_path = remote_path
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.lister_depot()
|
||||||
|
|
||||||
|
def lister_depot(self):
|
||||||
|
custom_env = os.environ.copy()
|
||||||
|
custom_env["BORG_PASSPHRASE"] = self.passphrase
|
||||||
|
|
||||||
|
self.log_str.emit("borg list " + self.depot + " --remote-path" + self.remote_path)
|
||||||
|
result = subprocess.run(["borg", "list", self.depot, "--remote-path", self.remote_path], env=custom_env, capture_output=True, text=True)
|
||||||
|
self.log_str.emit(result.stderr)
|
||||||
|
self.log_str.emit(result.stdout)
|
||||||
|
liste_backup = result.stdout.split("\n")
|
||||||
|
self.backups:list[str] = []
|
||||||
|
for backup in liste_backup:
|
||||||
|
try:
|
||||||
|
self.backups.append(backup.split()[0])
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_backups(self):
|
||||||
|
return self.backups
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
app = QApplication([])
|
||||||
|
widget = BorgBackupGui()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
59
borgbackup_gui/ConfigRepoGUI.py
Normal file
59
borgbackup_gui/ConfigRepoGUI.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QApplication, QPushButton, QLineEdit
|
||||||
|
from PySide6.QtCore import QSize, Signal, Slot
|
||||||
|
|
||||||
|
class ConfigRepoGUI(QWidget):
|
||||||
|
valider = Signal()
|
||||||
|
def __init__(self, passphrase="", remote_path="/usr/local/bin/borg", depot=""):
|
||||||
|
super().__init__()
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(QLabel("Configuration"))
|
||||||
|
self.repo = ConfigElementGUI("Depot", depot)
|
||||||
|
self.passphrase = ConfigElementGUI("Borg_passphrase", passphrase)
|
||||||
|
self.remote_path = ConfigElementGUI("--remote_path", remote_path)
|
||||||
|
layout.addWidget(self.repo)
|
||||||
|
layout.addWidget(self.passphrase)
|
||||||
|
layout.addWidget(self.remote_path)
|
||||||
|
self.valider_button = QPushButton("Valider")
|
||||||
|
layout.addWidget(self.valider_button)
|
||||||
|
self.valider_button.clicked.connect(self.envoyer_valider)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def get_repo(self)->str:
|
||||||
|
return self.repo.field.text()
|
||||||
|
|
||||||
|
def get_passphrase(self)->str:
|
||||||
|
return self.passphrase.field.text()
|
||||||
|
|
||||||
|
def get_remote_path(self)->str:
|
||||||
|
return self.remote_path.field.text()
|
||||||
|
|
||||||
|
def task_ended(self):
|
||||||
|
self.valider_button.setEnabled(True)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def envoyer_valider(self):
|
||||||
|
print("envoyer_valider")
|
||||||
|
self.valider.emit()
|
||||||
|
self.valider_button.setEnabled(False)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigElementGUI(QWidget):
|
||||||
|
def __init__(self, nom:str, valeur_par_défaut:str=""):
|
||||||
|
super().__init__()
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
layout.addWidget(QLabel(nom))
|
||||||
|
self.field = QLineEdit(valeur_par_défaut)
|
||||||
|
layout.addWidget(self.field)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(600, 50)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
app = QApplication([])
|
||||||
|
widget = ConfigRepoGUI()
|
||||||
|
widget.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
72
borgbackup_gui/ListBackupGUI.py
Normal file
72
borgbackup_gui/ListBackupGUI.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QApplication, QPushButton, QScrollArea
|
||||||
|
from PySide6.QtCore import Signal, QSize
|
||||||
|
|
||||||
|
class ListBackupGUI(QWidget):
|
||||||
|
selected = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, list_backup:list[str]):
|
||||||
|
super().__init__()
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(QLabel("Liste des sauvegardes"))
|
||||||
|
|
||||||
|
w_list_backup_elements = QWidget()
|
||||||
|
layout_list_backup_elements = QVBoxLayout()
|
||||||
|
|
||||||
|
|
||||||
|
self.listBackupElements:list[ListBackupElement] = []
|
||||||
|
for list_backup_element_name in list_backup:
|
||||||
|
list_backup_element = ListBackupElement(list_backup_element_name)
|
||||||
|
list_backup_element.selected.connect(self.is_selected)
|
||||||
|
self.listBackupElements.append(list_backup_element)
|
||||||
|
layout_list_backup_elements.addWidget(list_backup_element)
|
||||||
|
|
||||||
|
w_list_backup_elements.setLayout(layout_list_backup_elements)
|
||||||
|
|
||||||
|
scrollArea = QScrollArea()
|
||||||
|
scrollArea.setWidget(w_list_backup_elements)
|
||||||
|
layout.addWidget(scrollArea)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def is_selected(self, nom):
|
||||||
|
print(nom)
|
||||||
|
self.deactivate()
|
||||||
|
self.selected.emit(nom)
|
||||||
|
|
||||||
|
def deactivate(self):
|
||||||
|
for element in self.listBackupElements:
|
||||||
|
element.button.setEnabled(False)
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
for element in self.listBackupElements:
|
||||||
|
element.button.setEnabled(True)
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(400, 400)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ListBackupElement(QWidget):
|
||||||
|
selected = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, nom_backup:str):
|
||||||
|
super().__init__()
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
self.nom = nom_backup
|
||||||
|
layout.addWidget(QLabel(nom_backup))
|
||||||
|
self.button = QPushButton("Select")
|
||||||
|
self.button.clicked.connect(self.is_selected)
|
||||||
|
layout.addWidget(self.button)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def is_selected(self):
|
||||||
|
self.selected.emit(self.nom)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
app = QApplication([])
|
||||||
|
widget = ListBackupGUI(["Aujourd'hui", "hier"])
|
||||||
|
widget.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
9
borgbackup_gui/__init__.py
Normal file
9
borgbackup_gui/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import sys
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
from .BorgBackupGui import BorgBackupGui
|
||||||
|
|
||||||
|
def run_borgbackup_gui():
|
||||||
|
app = QApplication([])
|
||||||
|
widget = BorgBackupGui()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
BIN
dist/borgbackup_gui-0.1.0-py3-none-any.whl
vendored
Normal file
BIN
dist/borgbackup_gui-0.1.0-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
doc/Interface.png
Normal file
BIN
doc/Interface.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
9
pyproject.toml
Normal file
9
pyproject.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "borgbackup_gui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"PySide6"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
borgbackup-gui = "borgbackup_gui:run_borgbackup_gui"
|
||||||
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
borgbackup_gui @ file:///home/samuel/Projets/borgbackup_gui/dist/borgbackup_gui-0.1.0-py3-none-any.whl#sha256=5b2bebba4c3c9d2f08916803f8fb36641d845ccd0a45dc0c9baae605165eb0ba
|
||||||
|
build==1.5.1
|
||||||
|
packaging==26.2
|
||||||
|
pyproject_hooks==1.2.0
|
||||||
|
PySide6==6.11.1
|
||||||
|
PySide6_Addons==6.11.1
|
||||||
|
PySide6_Essentials==6.11.1
|
||||||
|
setuptools==83.0.0
|
||||||
|
shiboken6==6.11.1
|
||||||
Loading…
Reference in New Issue
Block a user