add a prune function

This commit is contained in:
Andreas Zweili 2019-04-28 20:01:17 +02:00
parent d87b1c953b
commit ef189be7cf
9 changed files with 238 additions and 9 deletions

View File

@ -101,7 +101,7 @@ class BackupThread(BorgQtThread):
self.command = ['borg', 'create', '--log-json', '--json',
('::'
+ self.prefix
+ '{now:%Y-%m-%d_%H:%M:%S}')]
+ '{now:%Y-%m-%d_%H:%M:%S,%f}')]
self.command.extend(self.includes)
if self.excludes:
self.command.extend(self.excludes)
@ -192,3 +192,24 @@ class MountThread(BorgQtThread):
def create_command(self):
self.command = ['borg', 'mount', '--log-json',
('::' + self.archive_name), self.mount_path]
class PruneThread(BorgQtThread):
"""Prunes the repository according to the given retention policy.
Args:
policy (dict) the name of the archive to restore.
"""
def __init__(self, policy):
self.policy = self._process_policy(policy)
super().__init__()
def create_command(self):
self.command = ['borg', 'prune', '--log-json']
self.command.extend(self.policy)
def _process_policy(self, raw_policy):
policy = []
for key, value in raw_policy.items():
policy.append('--keep-' + key + "=" + value)
return policy

View File

@ -126,6 +126,14 @@ class Config(QDialog):
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']:
@ -141,6 +149,14 @@ class Config(QDialog):
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']
@ -313,6 +329,14 @@ class Config(QDialog):
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."""
@ -357,6 +381,17 @@ class Config(QDialog):
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

View File

@ -99,10 +99,13 @@ class MainWindow(QMainWindow):
def background_backup(self):
self.config.read()
self.config._set_environment_variables()
thread = borg.BackupThread(self.config.includes,
backup_thread = borg.BackupThread(self.config.includes,
excludes=self.config.excludes,
prefix=self.config.prefix)
thread.run()
backup_thread.run()
if self.config.retention_policy_enabled:
prune_thread = borg.PruneThread(self.config.retention_policy)
prune_thread.run()
def get_selected_path(self, signal):
"""returns the path of the item selected in the file tree."""
@ -125,12 +128,19 @@ class MainWindow(QMainWindow):
return
try:
self._check_path()
thread = borg.BackupThread([self.src_path],
backup_thread = borg.BackupThread([self.src_path],
excludes=self.config.excludes,
prefix=self.config.prefix)
dialog = ProgressDialog(thread)
dialog.label_info.setText("Borg-Qt is currently creating an archive.")
dialog.exec_()
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_()
self.update_ui()
except BorgException as e:
show_error(e)

View File

@ -32,7 +32,7 @@
<bool>false</bool>
</property>
</widget>
<widget class="QTabWidget" name="tab_widget">
<widget class="QTabWidget" name="tab_policy">
<property name="geometry">
<rect>
<x>10</x>
@ -438,6 +438,131 @@
</layout>
</widget>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Retention Policy</string>
</attribute>
<widget class="QWidget" name="verticalLayoutWidget_4">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>541</width>
<height>191</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layout_policy">
<item>
<widget class="QCheckBox" name="check_policy_enabled">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Enable</string>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="layout_policy_details">
<item row="1" column="0">
<widget class="QLabel" name="label_policy_daily">
<property name="text">
<string>Daily:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spin_policy_daily">
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>7</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spin_policy_weekly">
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_policy_hourly">
<property name="text">
<string>Hourly:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_policy_weekly">
<property name="text">
<string>Weekly:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_policy_monthly">
<property name="text">
<string>Monthly:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="spin_policy_monthly">
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>12</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spin_policy_hourly">
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="spin_policy_yearly">
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_policy_yearly">
<property name="text">
<string>Yearly:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
</widget>
<resources/>

View File

@ -26,6 +26,14 @@ schedule_month = 0
schedule_time = 12:00:00
schedule_predefined_enabled = True
schedule_predefined_name = hourly
retention_policy_enabled = False
retention_policy = {
"hourly": "24",
"daily": "7",
"weekly": "4",
"monthly": "12",
"yearly": "1"
}
[borgqt]
includes = [

View File

@ -1,4 +1,3 @@
pyinstaller
PyQt5
borgbackup[fuse]==1.1.8
pytest

View File

@ -67,3 +67,15 @@ def archives(repository):
def target_path(tmpdir):
yield str(tmpdir)
remove_path(str(tmpdir))
@pytest.fixture
def create_archive():
def _create_archive(number_of_turns):
while number_of_turns > 0:
backup_thread = borg.BackupThread(['.'])
backup_thread.run()
number_of_turns -= 1
list_thread = borg.ListThread()
return list_thread.run()
return _create_archive

View File

@ -58,3 +58,13 @@ def test_mount(target_path, archives):
assert os.path.exists(
os.path.join(mount_path, os.path.realpath(__file__)))
os.system('borg umount ' + mount_path)
def test_prune(repository, create_archive):
archive_list = create_archive(2)
thread = borg.PruneThread({'hourly': '1'})
thread.run()
list_thread = borg.ListThread()
repo_archives = list_thread.run()
assert len(archive_list) > len(repo_archives)

View File

@ -121,3 +121,12 @@ def test_exclude_remove(form):
form.config.list_exclude.setCurrentRow(0)
form.config.remove_exclude()
assert (counter >= form.config.list_exclude.count())
def test_retention_config(form):
assert form.config.retention_policy_enabled == False
assert form.config.retention_policy['hourly'] == '24'
assert form.config.retention_policy['daily'] == '7'
assert form.config.retention_policy['weekly'] == '4'
assert form.config.retention_policy['monthly'] == '12'
assert form.config.retention_policy['yearly'] == '1'