diff --git a/borg_qt/borg_qt.py b/borg_qt/borg_qt.py
new file mode 100755
index 0000000..9a23c45
--- /dev/null
+++ b/borg_qt/borg_qt.py
@@ -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_())
diff --git a/borg_qt/config.py b/borg_qt/config.py
index 3a329ed..5d5130f 100644
--- a/borg_qt/config.py
+++ b/borg_qt/config.py
@@ -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()
diff --git a/borg_qt/main_window.py b/borg_qt/main_window.py
new file mode 100644
index 0000000..4afe169
--- /dev/null
+++ b/borg_qt/main_window.py
@@ -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_()
diff --git a/borg_qt/settings.py b/borg_qt/settings.py
new file mode 100644
index 0000000..22945cf
--- /dev/null
+++ b/borg_qt/settings.py
@@ -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()
diff --git a/borg_qt/static/UI/Settings.ui b/borg_qt/static/UI/Settings.ui
index 188fd35..7d69b70 100644
--- a/borg_qt/static/UI/Settings.ui
+++ b/borg_qt/static/UI/Settings.ui
@@ -54,27 +54,11 @@
10
10
541
- 251
+ 296
- -
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Password:
-
-
-
- -
+
-
@@ -88,46 +72,10 @@
-
-
+
- -
-
-
- Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData
-
-
- QLineEdit::Password
-
-
- false
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Port:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Username:
-
-
+
-
+
-
@@ -142,13 +90,23 @@
- -
+
-
- -
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Port:
+
+
- -
+
-
@@ -161,9 +119,108 @@
- -
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Username:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Password:
+
+
+
+ -
+
+
+ Qt::ImhNone
+
+
+ QLineEdit::Normal
+
+
+ false
+
+
+
+ -
+ -
+
+
+
+
+
+
+
+ Include
+
+
+
+
+ 10
+ 10
+ 541
+ 331
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Add file
+
+
+
+ ../icons/plus.svg../icons/plus.svg
+
+
+
+ -
+
+
+ Add folder
+
+
+
+ ../icons/plus.svg../icons/plus.svg
+
+
+
+ -
+
+
+ Remove
+
+
+
+ ../icons/minus.svg../icons/minus.svg
+
+
+
+
+
@@ -182,12 +239,12 @@
-
-
+
-
-
-
+
Add file
@@ -198,7 +255,7 @@
-
-
+
Add folder
@@ -209,14 +266,14 @@
-
-
+
- Add default
+ Restore defaults
-
-
+
Remove
diff --git a/tests/test_config.py b/tests/test_config.py
index 7a6003d..c44603a 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -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())