Add GUI functionality for the config

This commit is contained in:
Andreas Zweili 2019-01-23 21:34:08 +01:00
parent 4a1f97a096
commit eabaffec26
6 changed files with 496 additions and 132 deletions

19
borg_qt/borg_qt.py Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/python3
from PyQt5.QtWidgets import QApplication
import sys
from main_window import MainWindow
if __name__ == "__main__":
# creates the main application, only one of these is every needed
# in an application
app = QApplication(sys.argv)
# creates a new window, you can have multiple of these
window = MainWindow()
# show the window, they are hidden by default
window.show()
window.config.read()
# start the application
sys.exit(app.exec_())

View File

@ -2,12 +2,83 @@ import os
import configparser
import json
from PyQt5.QtWidgets import QDialog, QFileDialog
from PyQt5 import uic
from helper import BorgException
class Config():
class Config(QDialog):
def __init__(self):
self.list_values = ['excludes', 'includes']
# Setting all the PyQt relevant parts
super(QDialog, self).__init__()
dir_path = os.path.dirname(os.path.realpath(__file__))
ui_path = os.path.join(dir_path + '/static/UI/Settings.ui')
uic.loadUi(ui_path, self)
self.button_box.accepted.connect(self.accept)
self.button_include_file.clicked.connect(self.include_file)
self.button_include_directory.clicked.connect(self.include_directory)
self.button_exclude_file.clicked.connect(self.exclude_file)
self.button_exclude_directory.clicked.connect(self.exclude_directory)
self.button_remove_include.clicked.connect(self.remove_include)
self.button_remove_exclude.clicked.connect(self.remove_exclude)
self.button_restore_exclude_defaults.clicked.connect(
self.restore_exclude_defaults)
@property
def full_path(self):
if 'repository_path' in self.config['borgqt']:
if self.config['borgqt']['server']:
return self._create_server_path()
else:
return self.config['borgqt']['repository_path']
else:
return ""
@property
def repository_path(self):
return self._return_single_option('repository_path')
@property
def password(self):
return self._return_single_option('password')
@property
def includes(self):
return self._return_list_option('includes')
@property
def excludes(self):
return self._return_list_option('excludes')
@property
def server(self):
return self._return_single_option('server')
@property
def port(self):
return self._return_single_option('port')
@property
def user(self):
return self._return_single_option('user')
@property
def prefix(self):
return self._return_single_option('prefix')
def _return_single_option(self, option):
if option in self.config['borgqt']:
return self.config['borgqt'][option]
else:
return ""
def _return_list_option(self, option):
if option in self.config['borgqt']:
return json.loads(self.config['borgqt'][option])
else:
return []
def _get_path(self):
home = os.environ['HOME']
@ -21,8 +92,8 @@ class Config():
raise BorgException("Configuration file not found!")
def _set_environment_variables(self):
os.environ['BORG_REPO'] = str(self.repository_path)
os.environ['BORG_PASSPHRASE'] = str(self.password)
os.environ['BORG_REPO'] = self.full_path
os.environ['BORG_PASSPHRASE'] = self.password
def _create_server_path(self):
if not self.config['borgqt']['user']:
@ -37,6 +108,50 @@ class Config():
+ self.config['borgqt']['repository_path'])
return server_path
def _select_file(self):
dialog = QFileDialog
dialog.ExistingFile
file_path, ignore = dialog.getOpenFileName(
self, "Select Directory", os.getenv('HOME'), "All Files (*)")
return file_path
def _select_directory(self):
dialog = QFileDialog
dialog.DirectoryOnly
return dialog.getExistingDirectory(
self, "Select Directory", os.getenv('HOME'))
def include_file(self):
file_path = self._select_file()
if file_path:
self.list_include.addItem(file_path)
def include_directory(self):
directory_path = self._select_directory()
if directory_path:
self.list_include.addItem(directory_path)
def exclude_file(self):
file_path = self._select_file()
if file_path:
self.list_exclude.addItem(file_path)
def exclude_directory(self):
directory_path = self._select_directory()
if directory_path:
self.list_exclude.addItem(directory_path)
def remove_include(self):
self.list_include.takeItem(self.list_include.currentRow())
def remove_exclude(self):
self.list_exclude.takeItem(self.list_exclude.currentRow())
def restore_exclude_defaults(self):
self.list_exclude.clear()
default_excludes = json.loads(self.config['DEFAULT']['excludes'])
self.list_exclude.addItems(default_excludes)
def read(self):
"""Reads the config file
"""
@ -44,28 +159,47 @@ class Config():
self.config = configparser.ConfigParser()
self.config.read(self.path)
def apply(self):
for option, value in self.config.items('borgqt'):
setattr(self, option, value)
def set_form_values(self):
self.line_edit_repository_path.setText(self.repository_path)
self.line_edit_password.setText(self.password)
self.line_edit_prefix.setText(self.prefix)
self.line_edit_server.setText(self.server)
self.line_edit_port.setText(self.port)
self.line_edit_user.setText(self.user)
self.list_include.clear()
self.list_include.addItems(self.includes)
self.list_exclude.clear()
self.list_exclude.addItems(self.excludes)
for item in self.list_values:
setattr(self, item, json.loads(
self.config['borgqt'].get(item, '[]')))
def apply_options(self):
self.config['borgqt']['repository_path'] = self.line_edit_repository_path.text()
self.config['borgqt']['password'] = self.line_edit_password.text()
self.config['borgqt']['prefix'] = self.line_edit_prefix.text()
self.config['borgqt']['server'] = self.line_edit_server.text()
self.config['borgqt']['port'] = self.line_edit_port.text()
self.config['borgqt']['user'] = self.line_edit_user.text()
if self.config['borgqt']['server']:
self.repository_path = self._create_server_path()
excludes = []
for index in range(self.list_exclude.count()):
excludes.append(self.list_exclude.item(index).text())
includes = []
for index in range(self.list_include.count()):
includes.append(self.list_include.item(index).text())
self.config['borgqt']['includes'] = json.dumps(includes,
indent=4,
sort_keys=True)
self.config['borgqt']['excludes'] = json.dumps(excludes,
indent=4,
sort_keys=True)
self._set_environment_variables()
def write(self):
if self.server:
self.config['borgqt']['port'] = self.port
self.config['borgqt']['user'] = self.user
self.config['borgqt']['server'] = self.server
for item in self.list_values:
self.config['borgqt'][item] = json.dumps(
getattr(self, item), indent=4, sort_keys=True)
self.config['borgqt']['password'] = self.password
with open(self.path, 'w+') as configfile:
self.config.write(configfile)
def accept(self):
super().accept()
self.apply_options()
self.write()

26
borg_qt/main_window.py Normal file
View File

@ -0,0 +1,26 @@
import os
from PyQt5 import uic
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QMainWindow
from settings import Settings
from config import Config
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("Borg Interface")
QCoreApplication.setApplicationName("borg-qt")
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)
self.config = Config()
self.action_settings.triggered.connect(self.show_settings)
def show_settings(self):
self.config.set_form_values()
self.config.exec_()

67
borg_qt/settings.py Normal file
View File

@ -0,0 +1,67 @@
import os
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog
from PyQt5 import uic
class Settings(QDialog):
def __init__(self, config):
super(QDialog, self).__init__()
dir_path = os.path.dirname(os.path.realpath(__file__))
ui_path = os.path.join(dir_path + '/static/UI/Settings.ui')
uic.loadUi(ui_path, self)
self.config = config
self.read_config()
self.button_box.accepted.connect(self.read_inputs)
self.show()
def read_config(self):
try:
self.line_edit_repository_path.setText(self.config.repository_path)
self.line_edit_password.setText(self.config.password)
except AttributeError:
pass
try:
self.line_edit_prefix.setText(self.config.prefix)
except AttributeError:
pass
try:
self.line_edit_server.setText(self.config.server)
self.line_edit_port.setText(self.config.port)
self.line_edit_user.setText(self.config.user)
except AttributeError:
pass
try:
self.list_include.clear()
self.list_include.addItems(self.config.includes)
except AttributeError:
pass
try:
self.list_exclude.clear()
self.list_exclude.addItems(self.config.excludes)
except AttributeError:
pass
def read_inputs(self):
self.config.repository_path = self.line_edit_repository_path.text()
self.config.password = self.line_edit_password.text()
self.config.prefix = self.line_edit_prefix.text()
self.config.server = self.line_edit_server.text()
self.config.port = self.line_edit_port.text()
self.config.user = self.line_edit_user.text()
excludes = []
for index in range(self.list_exclude.count()):
excludes.append(self.list_exclude.item(index).text())
includes = []
for index in range(self.list_include.count()):
includes.append(self.list_include.item(index).text())
self.config.includes = includes
self.config.excludes = excludes
self.config.write()
self.config.apply()

View File

@ -54,27 +54,11 @@
<x>10</x>
<y>10</y>
<width>541</width>
<height>251</height>
<height>296</height>
</rect>
</property>
<layout class="QGridLayout" name="layout_general">
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="line_edit_path"/>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_prefix">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
@ -88,46 +72,10 @@
</widget>
</item>
<item row="7" column="0">
<widget class="QLineEdit" name="line_edit_user"/>
<widget class="QLineEdit" name="line_edit_server"/>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="line_edit_password">
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_user">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username:</string>
</property>
</widget>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="line_edit_repository_path"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_path">
@ -142,13 +90,23 @@
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<widget class="QLineEdit" name="line_edit_port"/>
</item>
<item row="5" column="0">
<widget class="QLineEdit" name="line_edit_server"/>
<item row="6" column="1">
<widget class="QLabel" name="label_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_server">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
@ -161,9 +119,108 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_user">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLineEdit" name="line_edit_password">
<property name="inputMethodHints">
<set>Qt::ImhNone</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLineEdit" name="line_edit_prefix"/>
</item>
<item row="9" column="0" colspan="2">
<widget class="QLineEdit" name="line_edit_user"/>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="tab_include">
<attribute name="title">
<string>Include</string>
</attribute>
<widget class="QWidget" name="verticalLayoutWidget_3">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>541</width>
<height>331</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layout_include">
<item>
<widget class="QListWidget" name="list_include"/>
</item>
<item>
<layout class="QHBoxLayout" name="layout_buttons_include">
<item>
<widget class="QPushButton" name="button_include_file">
<property name="text">
<string>Add file</string>
</property>
<property name="icon">
<iconset>
<normaloff>../icons/plus.svg</normaloff>../icons/plus.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_include_directory">
<property name="text">
<string>Add folder</string>
</property>
<property name="icon">
<iconset>
<normaloff>../icons/plus.svg</normaloff>../icons/plus.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_remove_include">
<property name="text">
<string>Remove</string>
</property>
<property name="icon">
<iconset>
<normaloff>../icons/minus.svg</normaloff>../icons/minus.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
@ -182,12 +239,12 @@
</property>
<layout class="QVBoxLayout" name="layout_exclude">
<item>
<widget class="QListView" name="list_exclude"/>
<widget class="QListWidget" name="list_exclude"/>
</item>
<item>
<layout class="QHBoxLayout" name="layout_buttons_exclude">
<item>
<widget class="QPushButton" name="button_add_file">
<widget class="QPushButton" name="button_exclude_file">
<property name="text">
<string>Add file</string>
</property>
@ -198,7 +255,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="button_add_folder">
<widget class="QPushButton" name="button_exclude_directory">
<property name="text">
<string>Add folder</string>
</property>
@ -209,14 +266,14 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="button_add_default">
<widget class="QPushButton" name="button_restore_exclude_defaults">
<property name="text">
<string>Add default</string>
<string>Restore defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_remove_item">
<widget class="QPushButton" name="button_remove_exclude">
<property name="text">
<string>Remove</string>
</property>

View File

@ -1,78 +1,139 @@
import os
import sys
import configparser
import unittest
from unittest.mock import MagicMock, patch
import warnings
import os
import configparser
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtTest import QTest
import context
from main_window import MainWindow
from config import Config
from helper import BorgException
class TestConfiguration(unittest.TestCase):
app = QApplication(sys.argv)
def fxn():
warnings.warn("deprecated", DeprecationWarning)
class BorgQtConfigTestCase(unittest.TestCase):
def setUp(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
fxn()
self.form = MainWindow()
self.dir_path = os.path.dirname(os.path.realpath(__file__))
self.config_path = os.path.join(self.dir_path,
'../docs/borg_qt.conf.example')
class TestConfiguration(BorgQtConfigTestCase):
def test_read_configuration(self):
config = Config()
config._get_path = MagicMock(return_value=self.config_path)
config.read()
self.form.config._get_path = MagicMock(return_value=self.config_path)
self.form.config.read()
parser = configparser.ConfigParser()
parser.read(self.config_path)
self.assertEqual(parser, config.config)
self.assertEqual(parser, self.form.config.config)
@patch('config.os.path')
def test_absent_config_file(self, mock_path):
mock_path.exists.return_value = False
with self.assertRaises(BorgException):
config = Config()
config._get_path()
self.form.config._get_path()
def test_absent_port(self):
config = Config()
config._get_path = MagicMock(return_value=self.config_path)
config.read()
config.config['borgqt']['port'] = ""
self.form.config._get_path = MagicMock(return_value=self.config_path)
self.form.config.read()
self.form.config.config['borgqt']['port'] = ""
with self.assertRaises(BorgException):
config._create_server_path()
self.form.config._create_server_path()
def test_absent_port(self):
config = Config()
config._get_path = MagicMock(return_value=self.config_path)
config.read()
config.config['borgqt']['user'] = ""
def test_absent_user(self):
self.form.config._get_path = MagicMock(return_value=self.config_path)
self.form.config.read()
self.form.config.config['borgqt']['user'] = ""
with self.assertRaises(BorgException):
config._create_server_path()
self.form.config._create_server_path()
def test_apply_settings(self):
config = Config()
config._get_path = MagicMock(return_value=self.config_path)
config.read()
config.apply()
self.assertIs(type(config.excludes), list)
self.assertEqual(str(config.repository_path), os.environ['BORG_REPO'])
self.assertEqual(str(config.password), os.environ['BORG_PASSPHRASE'])
self.form.config._get_path = MagicMock(return_value=self.config_path)
self.form.config.read()
self.form.config.apply_options()
self.assertIs(type(self.form.config.excludes), list)
self.assertEqual(self.form.config.full_path,
os.environ['BORG_REPO'])
self.assertEqual(self.form.config.password,
os.environ['BORG_PASSPHRASE'])
class TestWriteConfiguration(unittest.TestCase):
def setUp(self):
self.dir_path = os.path.dirname(os.path.realpath(__file__))
self.config_path = os.path.join(self.dir_path,
'../docs/borg_qt.conf.example')
self.config = Config()
self.config._get_path = MagicMock(return_value=self.config_path)
self.config.read()
self.config.apply()
self.config.path = '/tmp/test.conf'
class TestWriteConfiguration(BorgQtConfigTestCase):
def tearDown(self):
os.remove(self.config.path)
if os.path.exists(self.form.config.path):
os.remove(self.form.config.path)
def test_write_config(self):
self.config.write()
self.form.config._get_path = MagicMock(return_value=self.config_path)
self.form.config.read()
self.form.config.path = '/tmp/test.conf'
self.form.config.write()
config = configparser.ConfigParser()
config.read(self.config.path)
self.assertEqual(self.config.config['borgqt']['password'],
config.read(self.form.config.path)
self.assertEqual(self.form.config.config['borgqt']['password'],
config['borgqt']['password'])
class TestGuiConfiguration(BorgQtConfigTestCase):
def setUp(self):
super().setUp()
self.form.config.read()
self.form.config.path = '/tmp/test.conf'
def tearDown(self):
if os.path.exists(self.form.config.path):
os.remove(self.form.config.path)
def test_set_form_values(self):
self.form.config.set_form_values()
self.assertEqual(self.form.config.password,
self.form.config.line_edit_password.text())
def test_cancel_settings(self):
self.form.config.line_edit_password.clear()
QTest.keyClicks(self.form.config.line_edit_password, "bar")
cancel_button = self.form.config.button_box.button(
self.form.config.button_box.Cancel)
QTest.mouseClick(cancel_button, Qt.LeftButton)
self.assertEqual(self.form.config.password,
self.form.config.config['borgqt']['password'])
def test_ok_settings(self):
self.form.config.line_edit_password.clear()
QTest.keyClicks(self.form.config.line_edit_password, "bar")
ok_button = self.form.config.button_box.button(
self.form.config.button_box.Ok)
QTest.mouseClick(ok_button, Qt.LeftButton)
parser = configparser.ConfigParser()
parser.read(self.form.config.path)
self.assertEqual(self.form.config.line_edit_password.text(),
parser['borgqt']['password'])
def test_include_remove(self):
self.form.config.set_form_values()
counter = self.form.config.list_include.count()
self.form.config.list_include.setCurrentRow(0)
self.form.config.remove_include()
self.assertGreaterEqual(counter, self.form.config.list_include.count())
def test_exclude_remove(self):
self.form.config.set_form_values()
counter = self.form.config.list_exclude.count()
self.form.config.list_exclude.setCurrentRow(0)
self.form.config.remove_exclude()
self.assertGreaterEqual(counter, self.form.config.list_exclude.count())