add a prune function
This commit is contained in:
parent
d87b1c953b
commit
ef189be7cf
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pyinstaller
|
||||
PyQt5
|
||||
borgbackup[fuse]==1.1.8
|
||||
pytest
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue