borg-qt/borg_qt/main_window.py

298 lines
11 KiB
Python
Raw Normal View History

2019-01-23 21:34:08 +01:00
import os
import sys
2019-01-23 21:34:08 +01:00
from PyQt5 import uic
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import (QMainWindow, QFileSystemModel, QFileDialog,
QMessageBox)
2019-01-23 21:34:08 +01:00
from config import Config
2019-02-03 21:34:36 +01:00
from helper import (BorgException, show_error, convert_size, open_path,
create_path, remove_path, check_path)
2019-02-18 16:26:38 +01:00
from help import Help
2019-02-02 11:27:38 +01:00
import borg_interface as borg
2019-02-02 14:54:37 +01:00
from progress import ProgressDialog
2019-01-23 21:34:08 +01:00
class MainWindow(QMainWindow):
2019-01-27 14:49:31 +01:00
"""The main window of the application. It provides the various functions to
control BorgBackup."""
2019-01-23 21:34:08 +01:00
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
QCoreApplication.setApplicationName("borg-qt")
2019-01-27 14:49:31 +01:00
# Load the UI file to get the dialogs layout.
2019-01-23 21:34:08 +01:00
dir_path = os.path.dirname(os.path.realpath(__file__))
ui_path = os.path.join(dir_path + '/static/UI/MainWindow.ui')
uic.loadUi(ui_path, self)
2019-01-27 14:49:31 +01:00
# Set the window title after the UI has been loaded. Otherwise it gets
# overwritten.
self.setWindowTitle("Borg-Qt")
2019-01-23 21:34:08 +01:00
2019-01-27 14:49:31 +01:00
# Create a Config object for storing the configuration.
2019-01-23 21:34:08 +01:00
self.config = Config()
2019-02-03 21:34:36 +01:00
# list of mounted archives
self.mount_paths = []
2019-02-02 11:27:38 +01:00
# File tree
model = QFileSystemModel()
# model.setRootPath('/')
model.setRootPath(os.getenv('HOME'))
self.treeview_files.setModel(model)
self.treeview_files.expandAll()
self.treeview_files.setIndentation(20)
self.treeview_files.setColumnHidden(1, True)
self.treeview_files.setColumnHidden(2, True)
self.treeview_files.setColumnHidden(3, True)
# return the clicking on an item in the tree
self.treeview_files.clicked.connect(self.get_selected_path)
self.list_archive.setSortingEnabled(True)
2019-01-27 14:49:31 +01:00
# Connecting actions and buttons.
2019-01-23 21:34:08 +01:00
self.action_settings.triggered.connect(self.show_settings)
2019-02-02 11:27:38 +01:00
self.action_backup.triggered.connect(self.create_backup)
2019-02-02 14:54:37 +01:00
self.action_restore.triggered.connect(self.restore_backup)
2019-02-02 16:37:38 +01:00
self.action_delete.triggered.connect(self.delete_backup)
2019-02-03 21:34:36 +01:00
self.action_mount.triggered.connect(self.mount_backup)
2019-02-18 16:26:38 +01:00
self.action_help.triggered.connect(self.show_help)
2019-01-23 21:34:08 +01:00
def start(self):
2019-01-27 14:49:31 +01:00
"""This method is intendet to be used only once at the application
start. It reads the configuration file and sets the required
environment variables."""
try:
self.config.read()
2019-02-18 16:26:38 +01:00
# show the help window if needed and save it's answer
if not self.config.hide_help:
self.config.config['borgqt']['hide_help'] = (
str(self.show_help()))
self.config.write()
self.config._set_environment_variables()
2019-02-02 11:27:38 +01:00
self._update_archives()
self._update_repository_stats()
except BorgException as e:
show_error(e)
sys.exit(1)
2019-02-03 21:34:36 +01:00
def closeEvent(self, *args, **kwargs):
super(QMainWindow, self).closeEvent(*args, **kwargs)
2019-02-10 17:21:41 +01:00
# When the application gets close unmount all the archives and remove
# their paths.
self._umount_archives()
def _umount_archives(self):
2019-02-03 21:34:36 +01:00
if self.mount_paths:
for path in self.mount_paths:
if os.path.exists(path):
os.system('borg umount ' + path)
remove_path(path)
self.mount_paths = []
2019-02-03 21:34:36 +01:00
2019-01-23 21:34:08 +01:00
def show_settings(self):
2019-01-27 14:49:31 +01:00
"""Display the settings dialog."""
2019-01-23 21:34:08 +01:00
self.config.set_form_values()
self.config.exec_()
2019-02-02 11:27:38 +01:00
2019-02-18 13:00:11 +01:00
def background_backup(self):
self.config.read()
self.config._set_environment_variables()
2019-04-28 20:01:17 +02:00
backup_thread = borg.BackupThread(self.config.includes,
2019-02-18 13:00:11 +01:00
excludes=self.config.excludes,
prefix=self.config.prefix)
2019-04-28 20:01:17 +02:00
backup_thread.run()
if self.config.retention_policy_enabled:
prune_thread = borg.PruneThread(self.config.retention_policy)
prune_thread.run()
2019-02-18 13:00:11 +01:00
2019-02-02 11:27:38 +01:00
def get_selected_path(self, signal):
"""returns the path of the item selected in the file tree."""
self.src_path = self.treeview_files.model().filePath(signal)
def _check_path(self):
2019-02-10 17:21:41 +01:00
"""Makes sure that the user selected a path to backup."""
2019-02-02 11:27:38 +01:00
message = ("Please select a file or directory "
2019-02-14 22:55:16 +01:00
"before taking a backup.")
2019-02-02 11:27:38 +01:00
if not hasattr(self, 'src_path'):
raise BorgException(message)
def create_backup(self):
"""Creates a backup of the selected item in the treeview."""
if not self._check_mounts():
return
2019-02-02 11:27:38 +01:00
try:
self._check_path()
2019-04-28 20:01:17 +02:00
backup_thread = borg.BackupThread([self.src_path],
2019-02-02 14:54:37 +01:00
excludes=self.config.excludes,
prefix=self.config.prefix)
2019-04-28 20:01:17 +02:00
backup_dialog = ProgressDialog(backup_thread)
backup_dialog.label_info.setText("Borg-Qt is currently creating an"
" archive.")
backup_dialog.exec_()
if self.config.retention_policy_enabled:
prune_thread = borg.PruneThread(self.config.retention_policy)
prune_dialog = ProgressDialog(prune_thread)
prune_dialog.label_info.setText("Borg-Qt is currently pruning "
"the repository.")
prune_dialog.exec_()
2019-02-02 16:37:38 +01:00
self.update_ui()
2019-02-02 11:27:38 +01:00
except BorgException as e:
show_error(e)
2019-02-02 14:54:37 +01:00
def _get_target_path(self):
2019-02-02 16:37:38 +01:00
"""Opens a file dialog and returns the opened path."""
2019-02-02 14:54:37 +01:00
dlg = QFileDialog
dlg.DirectoryOnly
folder_name = str(dlg.getExistingDirectory(
self, "Select Directory", os.getenv('HOME')))
return folder_name
2019-02-18 16:26:38 +01:00
def show_help(self):
"""Diplays the help dialog with some informations about the
application."""
help_window = Help()
help_window.exec_()
return help_window.check_hide_enabled.isChecked()
2019-02-02 16:37:38 +01:00
@property
def selected_archive(self):
return self.list_archive.currentItem().text()
2019-02-02 14:54:37 +01:00
def restore_backup(self):
"""Restores a selected backup to the given path."""
2019-02-02 16:37:38 +01:00
try:
archive_name = self.selected_archive
target_path = self._get_target_path()
except AttributeError:
2019-02-14 22:55:16 +01:00
error = BorgException("Please create or select an archive first.")
2019-02-02 16:37:38 +01:00
archive_name = None
target_path = None
show_error(error)
2019-02-10 17:21:41 +01:00
# Only restore the backup if the target is writeable and the archive
# was selected.
if check_path(target_path) and archive_name:
2019-02-02 14:54:37 +01:00
try:
2019-02-02 16:37:38 +01:00
restore_path = os.path.join(target_path, archive_name)
2019-02-07 21:35:14 +01:00
create_path(restore_path)
2019-02-02 14:54:37 +01:00
thread = borg.RestoreThread(archive_name, restore_path)
dialog = ProgressDialog(thread)
2019-02-04 10:38:43 +01:00
dialog.label_info.setText(
"Borg-Qt is currently restoring a backup.")
2019-02-02 14:54:37 +01:00
dialog.exec_()
open_path(restore_path)
except BorgException as e:
show_error(e)
2019-02-07 21:35:14 +01:00
remove_path(restore_path)
2019-02-02 14:54:37 +01:00
2019-02-02 16:37:38 +01:00
def delete_backup(self):
"""Deletes the selected archive from the repository."""
if not self._check_mounts():
return
2019-02-02 16:37:38 +01:00
try:
archive_name = self.selected_archive
except AttributeError:
2019-02-14 22:55:16 +01:00
error = BorgException("Please create or select an archive first.")
2019-02-02 16:37:38 +01:00
archive_name = None
show_error(error)
2019-02-10 17:21:41 +01:00
# Only continue if an archive was selected.
2019-02-02 16:37:38 +01:00
if archive_name:
2019-02-10 17:21:41 +01:00
# Prompt the user before continuing.
if self.yes_no("Do you want to delete this archive?"):
try:
thread = borg.DeleteThread(archive_name)
dialog = ProgressDialog(thread)
dialog.label_info.setText(
2019-04-28 20:33:32 +02:00
"Borg-Qt is currently deleting an archive.")
dialog.exec_()
self.update_ui()
except BorgException as e:
show_error(e)
2019-02-02 16:37:38 +01:00
2019-02-02 11:27:38 +01:00
def _update_archives(self):
"""Lists all the archive names in the UI."""
2019-02-07 21:35:14 +01:00
thread = borg.ListThread()
2019-02-02 11:27:38 +01:00
self.list_archive.clear()
archive_names = []
2019-02-07 21:35:14 +01:00
for archive in thread.run():
2019-02-02 11:27:38 +01:00
archive_names.append(archive['name'])
self.list_archive.addItems(archive_names)
2019-02-02 16:37:38 +01:00
def update_ui(self):
2019-02-10 17:21:41 +01:00
"""Updates the archive list and repository stats in the UI."""
2019-02-02 11:27:38 +01:00
try:
self._update_archives()
2019-02-02 16:37:38 +01:00
self._update_repository_stats()
2019-02-02 11:27:38 +01:00
except BorgException as e:
show_error(e)
def _update_repository_stats(self):
2019-02-10 17:21:41 +01:00
"""Update the repository stats and display them in a human readable
format."""
2019-02-07 21:35:14 +01:00
thread = borg.InfoThread()
stats = thread.run()
2019-02-02 11:27:38 +01:00
self.label_repo_original_size.setText(
"Original Size: "
+ convert_size(stats['total_size']))
self.label_repo_compressed_size.setText(
"Compressed Size: "
+ convert_size(stats['total_csize']))
self.label_repo_deduplicated_size.setText(
"Deduplicated Size: "
+ convert_size(stats['unique_csize']))
2019-02-03 21:34:36 +01:00
def mount_backup(self):
2019-02-10 17:21:41 +01:00
"""Mount the selected archive in the tmp directory. If it succeeds the
mount_path gets written to a property of the main_window."""
2019-02-03 21:34:36 +01:00
try:
archive_name = self.selected_archive
except AttributeError:
2019-02-14 22:55:16 +01:00
error = BorgException("Please create or select an archive first.")
2019-02-03 21:34:36 +01:00
archive_name = None
show_error(error)
2019-02-10 17:21:41 +01:00
# only continue if the user selected an archive
2019-02-03 21:34:36 +01:00
if archive_name:
mount_path = os.path.join('/tmp/', archive_name)
create_path(mount_path)
2019-02-10 17:21:41 +01:00
# only continue if the mount_path is writeable
2019-02-03 21:34:36 +01:00
if os.access(mount_path, os.W_OK):
2019-02-07 21:35:14 +01:00
thread = borg.MountThread(archive_name, mount_path)
2019-02-03 21:34:36 +01:00
try:
2019-02-07 21:35:14 +01:00
thread.run()
self.mount_paths.append(mount_path)
2019-02-03 21:34:36 +01:00
open_path(mount_path)
except BorgException as e:
show_error(e)
2019-02-07 21:35:14 +01:00
remove_path(mount_path)
2019-02-03 21:34:36 +01:00
else:
2019-02-10 17:21:41 +01:00
# Opens the path in a file manager
2019-02-03 21:34:36 +01:00
open_path(mount_path)
def _check_mounts(self):
if self.mount_paths:
if self.yes_no("To proceed you need to unmout all "
"archives. Do you want to continue?"):
self._umount_archives()
return True
else:
return False
def yes_no(self, question):
2019-02-10 17:21:41 +01:00
"""Simple yes/no dialog.
Args:
question (str) The question to display to the user."""
button_reply = QMessageBox.question(self, 'Borg-Qt', question,
QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if button_reply == QMessageBox.Yes:
return True
else:
return False