borg-qt/borg_qt/config.py

421 lines
17 KiB
Python

import os
import configparser
import json
from distutils import util
from datetime import datetime
import subprocess
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QFileDialog
from PyQt5 import uic
from borg_qt.helper import BorgException
from borg_qt.systemd import SystemdFile
class Config(QDialog):
"""A class to read, display and write the Borg-Qt configuration."""
def __init__(self):
super(QDialog, self).__init__()
# Load the UI file to get the dialogs layout.
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)
# Connect all the button and actions.
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)
weekdays = ['', 'Monday', 'Thuesday', 'Weekdays', 'Thursday', 'Friday',
'Saturday', 'Sunday']
self.combo_schedule_weekday.addItems(weekdays)
months = ['', 'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November',
'December']
self.combo_schedule_month.addItems(months)
schedules = ['hourly', 'daily', 'weekly', 'monthly']
self.combo_schedule_predefined.addItems(schedules)
@property
def full_path(self):
"""returns the repository path or the repository server path if a
server was provided in the configuration."""
if self._return_single_option('repository_path'):
if self._return_single_option('server'):
return self._create_server_path()
else:
return self._return_single_option('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')
@property
def schedule_enabled(self):
return util.strtobool(self._return_single_option('schedule_enabled'))
@property
def schedule_predefined_enabled(self):
return util.strtobool(
self._return_single_option('schedule_predefined_enabled'))
@property
def schedule_custom_enabled(self):
return util.strtobool(
self._return_single_option('schedule_custom_enabled'))
@property
def schedule_time(self):
return self._return_single_option('schedule_time')
@property
def schedule_weekday(self):
return int(self._return_single_option('schedule_weekday'))
@property
def schedule_month(self):
return int(self._return_single_option('schedule_month'))
@property
def schedule_date(self):
return int(self._return_single_option('schedule_date'))
@property
def schedule_predefined_name(self):
return self._return_single_option('schedule_predefined_name')
@property
def hide_help(self):
return util.strtobool(self._return_single_option('hide_help'))
@property
def retention_policy_enabled(self):
return util.strtobool(self._return_single_option('retention_policy_enabled'))
@property
def retention_policy(self):
return self._return_dict_option('retention_policy')
def _return_single_option(self, option):
"""Gets the provided option from the configparser object."""
if option in self.config['borgqt']:
return self.config['borgqt'][option]
else:
return ""
def _return_list_option(self, option):
"""Reads the provided option from the configparser object and returns
it as a list."""
if option in self.config['borgqt']:
return json.loads(self.config['borgqt'][option])
else:
return []
def _return_dict_option(self, option):
"""Reads the provided option from the configparser object and returns
it as a dict."""
if option in self.config['borgqt']:
return json.loads(self.config['borgqt'][option])
else:
return {}
def _get_path(self):
"""searches for the configuration file and returns its full path."""
home = os.environ['HOME']
dir_path = os.path.dirname(os.path.realpath(__file__))
if os.path.exists(os.path.join(home, '.config/borg_qt/borg_qt.conf')):
return os.path.join(home, '.config/borg_qt/borg_qt.conf')
elif os.path.exists(os.path.join(dir_path, 'borg_qt.conf')):
return os.path.join(dir_path, 'borg_qt.conf')
else:
raise BorgException("Configuration file not found!")
def _set_environment_variables(self):
os.environ['BORG_REPO'] = self.full_path
os.environ['BORG_PASSPHRASE'] = self.password
def _create_server_path(self):
"""creates the full server path from the server, user and port
options."""
if not self._return_single_option('user'):
raise BorgException("User is missing in config.")
if not self._return_single_option('port'):
raise BorgException("Port is missing in config.")
server_path = ('ssh://'
+ self.config['borgqt']['user']
+ "@"
+ self.config['borgqt']['server']
+ ":"
+ self.config['borgqt']['port']
+ self.config['borgqt']['repository_path'])
return server_path
def _select_file(self):
"""Qt dialog to select an exisiting file."""
dialog = QFileDialog
dialog.ExistingFile
file_path, _ = dialog.getOpenFileName(
self, "Select Directory", os.getenv('HOME'), "All Files (*)")
return file_path
def _select_directory(self):
"""Qt dialog to select directories."""
dialog = QFileDialog
dialog.DirectoryOnly
return dialog.getExistingDirectory(
self, "Select Directory", os.getenv('HOME'))
def _create_service(self):
self.service = SystemdFile('borg_qt.service')
self.service.content['Service'] = {}
self.service.content['Unit']['Description'] = ("Runs Borg-Qt once in "
"the backround to take "
"a backup according to "
"the configuration.")
self.service.content['Service']['Type'] = 'oneshot'
process = subprocess.run(["which", "borg_qt"], stdout=subprocess.PIPE,
encoding='utf8')
output = process.stdout.strip()
self.service.content['Service']['ExecStart'] = output + ' -B'
self.service.write()
def _create_timer(self, schedule_interval):
self.timer = SystemdFile('borg_qt.timer')
self.timer.content['Timer'] = {}
self.timer.content['Install'] = {}
self.timer.content['Unit']['Description'] = ("Starts the "
"borg_qt.service "
"according to the "
"configured "
"schedule.")
self.timer.content['Timer']['OnCalendar'] = schedule_interval
self.timer.content['Timer']['Persistent'] = 'true'
self.timer.content['Install']['WantedBy'] = 'timers.target'
self.timer.write()
def _parse_schedule_interval(self):
if self.schedule_predefined_enabled:
return self.schedule_predefined_name
if self.schedule_custom_enabled:
if self.schedule_date > 0:
date = str(self.schedule_date)
else:
date = "*"
if self.schedule_month > 0:
month = str(self.schedule_month)
else:
month = "*"
if self.schedule_weekday > 0:
weekday = self.combo_schedule_weekday.currentText()
else:
weekday = ""
date_string = (weekday
+ " "
+ "*"
+ "-"
+ month
+ "-"
+ date
+ " "
+ self.schedule_time)
return date_string
def include_file(self):
"""add a file to the include list if the selected path is not empty."""
file_path = self._select_file()
if file_path:
self.list_include.addItem(file_path)
def include_directory(self):
"""add a directory to the include list if the selected path is not
empty."""
directory_path = self._select_directory()
if directory_path:
self.list_include.addItem(directory_path)
def exclude_file(self):
"""add a file to the exclude list if the selected path is not empty."""
file_path = self._select_file()
if file_path:
self.list_exclude.addItem(file_path)
def exclude_directory(self):
"""add a file to the exclude list if the selected path is not empty."""
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"""
self.path = self._get_path()
self.config = configparser.ConfigParser()
self.config.read(self.path)
def set_form_values(self):
# set the general tab values
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)
# set the include tab values
self.list_include.clear()
self.list_include.addItems(self.includes)
# set the include tab values
self.list_exclude.clear()
self.list_exclude.addItems(self.excludes)
# set schedule tab values
if self.schedule_enabled:
self.check_schedule_enabled.setChecked(True)
if self.schedule_custom_enabled:
self.radio_schedule_custom_enabled.setChecked(True)
_time = datetime.strptime(self.schedule_time, '%H:%M:%S')
self.time_schedule_time.setTime(_time.time())
self.combo_schedule_weekday.setCurrentIndex(self.schedule_weekday)
self.combo_schedule_month.setCurrentIndex(self.schedule_month)
index = self.combo_schedule_predefined.findText(
self.schedule_predefined_name, Qt.MatchFixedString)
self.combo_schedule_predefined.setCurrentIndex(index)
self.spin_schedule_date.setValue(self.schedule_date)
if self.retention_policy_enabled:
self.check_policy_enabled.setChecked(True)
policy = self.retention_policy
self.spin_policy_hourly.setValue(int(policy['hourly']))
self.spin_policy_daily.setValue(int(policy['daily']))
self.spin_policy_weekly.setValue(int(policy['weekly']))
self.spin_policy_monthly.setValue(int(policy['monthly']))
self.spin_policy_yearly.setValue(int(policy['yearly']))
def apply_options(self):
"""Writes the changed options back into the configparser object."""
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()
self.config['borgqt']['schedule_enabled'] = (
str(self.check_schedule_enabled.isChecked()))
self.config['borgqt']['schedule_predefined_enabled'] = (
str(self.radio_schedule_predefined_enabled.isChecked()))
self.config['borgqt']['schedule_custom_enabled'] = (
str(self.radio_schedule_custom_enabled.isChecked()))
self.config['borgqt']['schedule_time'] = (
self.time_schedule_time.time().toString())
self.config['borgqt']['schedule_weekday'] = (
str(self.combo_schedule_weekday.currentIndex()))
self.config['borgqt']['schedule_month'] = (
str(self.combo_schedule_month.currentIndex()))
self.config['borgqt']['schedule_date'] = self.spin_schedule_date.text()
self.config['borgqt']['schedule_predefined_name'] = (
self.combo_schedule_predefined.currentText())
# Workaraound to get all items of a QListWidget as a list
excludes = []
for index in range(self.list_exclude.count()):
excludes.append(self.list_exclude.item(index).text())
# Workaraound to get all items of a QListWidget as a list
includes = []
for index in range(self.list_include.count()):
includes.append(self.list_include.item(index).text())
# Configparser doesn't know about list therefore we store them as json
# strings
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.config['borgqt']['retention_policy_enabled'] = (
str(self.check_policy_enabled.isChecked()))
retention_policy = {}
retention_policy['hourly'] = self.spin_policy_hourly.text()
retention_policy['daily'] = self.spin_policy_daily.text()
retention_policy['weekly'] = self.spin_policy_weekly.text()
retention_policy['monthly'] = self.spin_policy_monthly.text()
retention_policy['yearly'] = self.spin_policy_yearly.text()
self.config['borgqt']['retention_policy'] = json.dumps(retention_policy,
indent=4,
sort_keys=True)
self._set_environment_variables()
# create and enable the required systemd files
# if it is not enable make sure that the timer is disabled.
if self.schedule_enabled:
self._create_service()
self._create_timer(self._parse_schedule_interval())
self.timer.enable()
else:
active_timer_path = os.path.join(
os.environ['HOME'],
'.config/systemd/user/timer.targets.wants/borg_qt.timer')
if os.path.exists(active_timer_path):
self.timer.disable()
def write(self):
"""Write the configparser object back to the config file."""
with open(self.path, 'w+') as configfile:
self.config.write(configfile)
def accept(self):
"""Extend the built in accept method to apply and write the new
options."""
super().accept()
self.apply_options()
self.write()