mirror of
https://github.com/borgbase/vorta
synced 2025-03-15 08:29:42 +00:00
Add backup from SystemTray.
This commit is contained in:
parent
6b00d73dff
commit
af453d8a6c
11 changed files with 369 additions and 120 deletions
|
@ -35,7 +35,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>801</width>
|
||||
<height>541</height>
|
||||
<height>561</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
@ -154,6 +154,24 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>250</height>
|
||||
<height>287</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -17,7 +17,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
|
@ -63,31 +63,8 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Select Encryption Mode</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="saveButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
|
@ -112,9 +89,20 @@
|
|||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="errorText">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
@ -134,8 +122,39 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="saveButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="title">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Initialize New Backup Repository</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
|
|
@ -17,14 +17,27 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Choose the folders to back up.</string>
|
||||
<string>Choose folders to back up.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
|
||||
<item>
|
||||
<widget class="QListWidget" name="sourceDirectoriesWidget"/>
|
||||
<widget class="QListWidget" name="sourceDirectoriesWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -46,6 +59,53 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="topMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Exclude Patterns (<a href="https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns"><span style=" text-decoration: underline; color:#0000ff;">more</span></a>)</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Exclude If Present (exclude folders with these files)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPlainTextEdit" name="excludePatternsField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>*/.DS_Store</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPlainTextEdit" name="excludeIfPresentField">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>.nobackup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from vorta.main_window import MainWindow
|
||||
from vorta.tray_menu import TrayMenu
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.thread = None
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
menu = TrayMenu(app)
|
||||
|
||||
ex = MainWindow()
|
||||
ex.show()
|
||||
sys.exit(app.exec_())
|
||||
|
|
|
@ -2,10 +2,16 @@ import json
|
|||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import platform
|
||||
from datetime import datetime as dt
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from .models import SourceDirModel, BackupProfileModel
|
||||
|
||||
|
||||
class BorgThread(QtCore.QThread):
|
||||
updated = QtCore.pyqtSignal(str)
|
||||
|
@ -21,23 +27,31 @@ class BorgThread(QtCore.QThread):
|
|||
cmd[0] = meipass_borg
|
||||
self.cmd = cmd
|
||||
|
||||
print(cmd)
|
||||
|
||||
env = os.environ.copy()
|
||||
if params.get('password'):
|
||||
env['BORG_HOSTNAME_IS_UNIQUE'] = '1'
|
||||
if params.get('password') and params['password']:
|
||||
env['BORG_PASSPHRASE'] = params['password']
|
||||
|
||||
if params.get('ssh_key'):
|
||||
if params.get('ssh_key') and params['ssh_key']:
|
||||
env['BORG_RSH'] = f'ssh -i ~/.ssh/{params["ssh_key"]}'
|
||||
|
||||
self.env = env
|
||||
self.params = params
|
||||
|
||||
def run(self):
|
||||
with Popen(self.cmd, stdout=PIPE, stderr=PIPE, bufsize=1, universal_newlines=True, env=self.env) as p:
|
||||
for line in p.stderr:
|
||||
parsed = json.loads(line)
|
||||
if parsed['type'] == 'log_message':
|
||||
self.updated.emit(f'{parsed["levelname"]}: {parsed["message"]}')
|
||||
elif parsed['type'] == 'file_status':
|
||||
self.updated.emit(f'{parsed["path"]} ({parsed["status"]})')
|
||||
print(line)
|
||||
try:
|
||||
parsed = json.loads(line)
|
||||
if parsed['type'] == 'log_message':
|
||||
self.updated.emit(f'{parsed["levelname"]}: {parsed["message"]}')
|
||||
elif parsed['type'] == 'file_status':
|
||||
self.updated.emit(f'{parsed["path"]} ({parsed["status"]})')
|
||||
except json.decoder.JSONDecodeError:
|
||||
self.updated.emit(line.strip())
|
||||
|
||||
p.wait()
|
||||
stdout = p.stdout.read()
|
||||
|
@ -51,3 +65,65 @@ class BorgThread(QtCore.QThread):
|
|||
result['data'] = {}
|
||||
|
||||
self.result.emit(result)
|
||||
|
||||
@classmethod
|
||||
def create_thread_factory(cls):
|
||||
"""`borg create` is called from different places and need preparation.
|
||||
Centralize it here and return a thread to the caller.
|
||||
"""
|
||||
ret = {
|
||||
'ok': False,
|
||||
}
|
||||
profile = BackupProfileModel.get(id=1)
|
||||
app = QApplication.instance()
|
||||
n_backup_folders = SourceDirModel.select().count()
|
||||
|
||||
if app.thread and app.thread.isRunning():
|
||||
ret['message'] = 'Backup is already in progress.'
|
||||
return ret
|
||||
|
||||
if n_backup_folders == 0:
|
||||
ret['message'] = 'Add some folders to back up first.'
|
||||
return ret
|
||||
|
||||
if profile.repo is None:
|
||||
ret['message'] = 'Add a remote backup repository first.'
|
||||
return ret
|
||||
|
||||
cmd = ['borg', 'create', '--list', '--info', '--log-json', '--json', '-C', profile.compression]
|
||||
|
||||
# Add excludes
|
||||
# Inspired by borgmatic/borgmatic/borg/create.py
|
||||
exclude_dirs = []
|
||||
for p in profile.exclude_patterns.split('\n'):
|
||||
if p.strip():
|
||||
expanded_directory = os.path.expanduser(p.strip())
|
||||
exclude_dirs.append(expanded_directory)
|
||||
|
||||
if exclude_dirs:
|
||||
pattern_file = tempfile.NamedTemporaryFile('w')
|
||||
pattern_file.write('\n'.join(exclude_dirs))
|
||||
pattern_file.flush()
|
||||
cmd.extend(['--exclude-from', pattern_file.name])
|
||||
|
||||
for f in profile.exclude_if_present.split('\n'):
|
||||
if f.strip():
|
||||
cmd.extend(['--exclude-if-present', f.strip()])
|
||||
|
||||
# Add repo url and source dirs.
|
||||
cmd.append(f'{profile.repo.url}::{platform.node()}-{dt.now().isoformat()}')
|
||||
|
||||
for f in SourceDirModel.select():
|
||||
cmd.append(f.dir)
|
||||
|
||||
params = {'password': profile.repo.password,
|
||||
'pattern_file': pattern_file}
|
||||
|
||||
app.thread = cls(app, cmd, params)
|
||||
ret['message'] = 'Starting Backup.'
|
||||
ret['ok'] = True
|
||||
ret['thread'] = app.thread
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import os
|
||||
import platform
|
||||
from datetime import datetime as dt
|
||||
import sys
|
||||
from dateutil import parser
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5 import uic
|
||||
|
||||
from .config import APP_NAME, remove_config
|
||||
from PyQt5.QtWidgets import QApplication, QShortcut
|
||||
from PyQt5 import uic, QtCore
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
from .config import APP_NAME
|
||||
from .models import SnapshotModel, BackupProfileModel, SourceDirModel
|
||||
from .borg_runner import BorgThread
|
||||
from .repo_tab import RepoTab
|
||||
|
@ -24,6 +22,7 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.setupUi(self)
|
||||
self.setWindowTitle(APP_NAME)
|
||||
self.profile = BackupProfileModel.get(id=1)
|
||||
self.app = QApplication.instance()
|
||||
|
||||
self.repoTab = RepoTab(self.repoTabSlot)
|
||||
self.repoTab.repo_changed.connect(lambda: self.snapshotTab.populate())
|
||||
|
@ -32,6 +31,21 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.snapshotTab = SnapshotTab(self.snapshotTabSlot)
|
||||
|
||||
self.createStartBtn.clicked.connect(self.create_action)
|
||||
self.cancelButton.clicked.connect(self.cancel_create_action)
|
||||
|
||||
QShortcut(QKeySequence("Ctrl+W"), self).activated.connect(self.on_close_window)
|
||||
QShortcut(QKeySequence("Ctrl+Q"), self).activated.connect(self.on_close_window)
|
||||
|
||||
# Connect to existing thread.
|
||||
if self.app.thread and self.app.thread.isRunning():
|
||||
self.createStartBtn.setEnabled(False)
|
||||
self.cancelButton.setEnabled(True)
|
||||
self.set_status('Connected to existing backup process.', progress_max=0)
|
||||
self.app.thread.updated.connect(self.create_update_log)
|
||||
self.app.thread.result.connect(self.create_get_result)
|
||||
|
||||
def on_close_window(self):
|
||||
self.close()
|
||||
|
||||
def set_status(self, text=None, progress_max=None):
|
||||
if text:
|
||||
|
@ -41,22 +55,12 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.createProgressText.repaint()
|
||||
|
||||
def create_action(self):
|
||||
n_backup_folders = SourceDirModel.select().count()
|
||||
if n_backup_folders == 0:
|
||||
self.set_status('Add some folders to back up first.')
|
||||
return
|
||||
self.set_status('Starting Backup.', progress_max=0)
|
||||
self.createStartBtn.setEnabled(False)
|
||||
self.createStartBtn.repaint()
|
||||
|
||||
repo = self.profile.repo
|
||||
cmd = ['borg', 'create', '--list', '--info', '--log-json', '--json', '-C', self.profile.compression,
|
||||
f'{repo.url}::{platform.node()}-{dt.now().isoformat()}'
|
||||
]
|
||||
for f in SourceDirModel.select():
|
||||
cmd.append(f.dir)
|
||||
|
||||
thread = BorgThread(self, cmd, {})
|
||||
thread_msg = BorgThread.create_thread_factory()
|
||||
if thread_msg['ok']:
|
||||
self.set_status(thread_msg['message'], progress_max=0)
|
||||
self.createStartBtn.setEnabled(False)
|
||||
self.createStartBtn.repaint()
|
||||
thread = thread_msg['thread']
|
||||
thread.updated.connect(self.create_update_log)
|
||||
thread.result.connect(self.create_get_result)
|
||||
thread.start()
|
||||
|
@ -64,19 +68,31 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
def create_update_log(self, text):
|
||||
self.set_status(text)
|
||||
|
||||
def cancel_create_action(self):
|
||||
try:
|
||||
self.app.thread.terminate()
|
||||
self.app.thread.wait()
|
||||
self.createStartBtn.setEnabled(True)
|
||||
self.createStartBtn.repaint()
|
||||
self.set_status(progress_max=100)
|
||||
except:
|
||||
print('no thread')
|
||||
|
||||
def create_get_result(self, result):
|
||||
self.createStartBtn.setEnabled(True)
|
||||
self.createStartBtn.repaint()
|
||||
self.set_status(progress_max=100)
|
||||
if result['returncode'] == 0:
|
||||
self.set_status(progress_max=100)
|
||||
new_snapshot = SnapshotModel(
|
||||
new_snapshot, created = SnapshotModel.get_or_create(
|
||||
snapshot_id=result['data']['archive']['id'],
|
||||
name=result['data']['archive']['name'],
|
||||
time=parser.parse(result['data']['archive']['start']),
|
||||
repo=self.profile.repo
|
||||
defaults={
|
||||
'name':result['data']['archive']['name'],
|
||||
'time':parser.parse(result['data']['archive']['start']),
|
||||
'repo':self.profile.repo
|
||||
}
|
||||
)
|
||||
new_snapshot.save()
|
||||
if 'cache' in result['data']:
|
||||
if 'cache' in result['data'] and created:
|
||||
stats = result['data']['cache']['stats']
|
||||
repo = self.profile.repo
|
||||
repo.total_size = stats['total_size']
|
||||
|
|
|
@ -28,6 +28,9 @@ class BackupProfileModel(peewee.Model):
|
|||
repo = peewee.ForeignKeyField(RepoModel, default=None, null=True)
|
||||
ssh_key = peewee.CharField(default=None, null=True)
|
||||
compression = peewee.CharField(default='lz4')
|
||||
exclude_patterns = peewee.TextField(null=True)
|
||||
exclude_if_present = peewee.TextField(null=True)
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
|
|
|
@ -1,26 +1,60 @@
|
|||
import os
|
||||
from PyQt5 import uic
|
||||
from .utils import get_private_keys, get_relative_asset
|
||||
from .borg_runner import BorgThread
|
||||
|
||||
uifile = get_relative_asset('UI/repoadd.ui')
|
||||
AddRepoUI, AddRepoBase = uic.loadUiType(uifile)
|
||||
|
||||
|
||||
class AddRepoWindow(AddRepoBase, AddRepoUI):
|
||||
connection_message = 'Setting up new repo...'
|
||||
cmd = ["borg", "init", "--log-json"]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.closeButton.clicked.connect(self.close)
|
||||
self.saveButton.clicked.connect(self.validate)
|
||||
self.saveButton.clicked.connect(self.run)
|
||||
|
||||
self.init_encryption()
|
||||
self.init_ssh_key()
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
out = dict(
|
||||
ssh_key=self.sshComboBox.currentData(),
|
||||
repo_url=self.repoURL.text(),
|
||||
password=self.passwordLineEdit.text()
|
||||
)
|
||||
if self.__class__ == AddRepoWindow:
|
||||
out['encryption'] = self.encryptionComboBox.currentData()
|
||||
return out
|
||||
|
||||
def run(self):
|
||||
if self.validate():
|
||||
self.set_status(self.connection_message)
|
||||
cmd = self.cmd + [self.values['repo_url']]
|
||||
thread = BorgThread(self, cmd, self.values)
|
||||
thread.updated.connect(self.set_status)
|
||||
thread.result.connect(self.run_result)
|
||||
thread.start()
|
||||
|
||||
def set_status(self, text):
|
||||
self.errorText.setText(text)
|
||||
self.errorText.repaint()
|
||||
|
||||
def run_result(self, result):
|
||||
if result['returncode'] == 0:
|
||||
self.result = result
|
||||
self.accept()
|
||||
|
||||
def init_encryption(self):
|
||||
self.encryptionComboBox.model().item(0).setEnabled(False)
|
||||
self.encryptionComboBox.addItem('Repokey-Blake2 (Recommended)', 'repokey-blake2')
|
||||
self.encryptionComboBox.addItem('Repokey-Blake2 (Recommended, key stored remotely)', 'repokey-blake2')
|
||||
self.encryptionComboBox.addItem('Repokey', 'repokey')
|
||||
self.encryptionComboBox.addItem('Keyfile-Blake2 (Key stored locally)', 'keyfile-blake2')
|
||||
self.encryptionComboBox.addItem('Keyfile', 'keyfile')
|
||||
self.encryptionComboBox.addItem('None (not recommended', 'none')
|
||||
|
||||
def init_ssh_key(self):
|
||||
|
@ -29,34 +63,27 @@ class AddRepoWindow(AddRepoBase, AddRepoUI):
|
|||
self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]}:{key["fingerprint"]})', key['filename'])
|
||||
|
||||
def validate(self):
|
||||
if len(self.repoURL.text()) < 5:
|
||||
self.errorText.setText('Please enter a repo URL.')
|
||||
return
|
||||
if len(self.values['repo_url']) < 5 or ':' not in self.values['repo_url']:
|
||||
self.set_status('Please enter a valid repo URL including hostname and path.')
|
||||
return False
|
||||
|
||||
if self.encryptionComboBox.isVisible() and self.encryptionComboBox.currentData() is None:
|
||||
self.errorText.setText('Please choose an encryption mode.')
|
||||
return
|
||||
if self.__class__ == AddRepoWindow:
|
||||
if self.values['encryption'] != 'none' and len(self.values['password']) < 8:
|
||||
self.set_status('Please use a longer password.')
|
||||
return False
|
||||
|
||||
self.cmd = ["borg", "init", "--log-json", f"--encryption={params['encryption']}", params['repo_url']]
|
||||
self.cmd.append(f"--encryption={self.values['encryption']}")
|
||||
|
||||
def get_values(self):
|
||||
return {
|
||||
'ssh_key': self.sshComboBox.currentData(),
|
||||
'encryption': self.encryptionComboBox.currentData(),
|
||||
'repo_url': self.repoURL.text(),
|
||||
'password': self.passwordLineEdit.text()
|
||||
}
|
||||
return True
|
||||
|
||||
|
||||
class ExistingRepoWindow(AddRepoWindow):
|
||||
connection_message = 'Validating existing repo...'
|
||||
cmd = ["borg", "list", "--json"]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.encryptionComboBox.hide()
|
||||
self.encryptionLabel.hide()
|
||||
self.title.setText('Connect to existing Repository')
|
||||
|
||||
def get_values(self):
|
||||
return {
|
||||
'ssh_key': self.sshComboBox.currentData(),
|
||||
'repo_url': self.repoURL.text(),
|
||||
'password': self.passwordLineEdit.text()
|
||||
}
|
||||
|
|
|
@ -74,41 +74,26 @@ class RepoTab(RepoBase, RepoUI):
|
|||
def repo_select_action(self, index):
|
||||
if index <= 2:
|
||||
if index == 1:
|
||||
repo_add_window = AddRepoWindow()
|
||||
window = AddRepoWindow()
|
||||
else:
|
||||
repo_add_window = ExistingRepoWindow()
|
||||
window = ExistingRepoWindow()
|
||||
|
||||
repo_add_window.setParent(self, QtCore.Qt.Sheet)
|
||||
repo_add_window.show()
|
||||
if repo_add_window.exec_():
|
||||
params = repo_add_window.get_values()
|
||||
|
||||
if index == 1:
|
||||
cmd = ["borg", "init", "--log-json", f"--encryption={params['encryption']}", params['repo_url']]
|
||||
else:
|
||||
cmd = ["borg", "list", "--json", params['repo_url']]
|
||||
|
||||
self.set_status('Connecting to repo...', 0)
|
||||
thread = BorgThread(self, cmd, params)
|
||||
thread.updated.connect(self.repo_add_update_log)
|
||||
thread.result.connect(self.repo_add_result)
|
||||
thread.start()
|
||||
window.setParent(self, QtCore.Qt.Sheet)
|
||||
window.show()
|
||||
if window.exec_():
|
||||
self.process_new_repo(window.result)
|
||||
else:
|
||||
self.profile.repo = self.repoSelector.currentData()
|
||||
self.profile.save()
|
||||
self.init_repo_stats()
|
||||
|
||||
def repo_add_update_log(self, text):
|
||||
self.set_status(text)
|
||||
|
||||
def repo_add_result(self, result):
|
||||
def process_new_repo(self, result):
|
||||
if result['returncode'] == 0:
|
||||
self.set_status('Successfully connected to repo.', 100)
|
||||
new_repo, _ = RepoModel.get_or_create(
|
||||
url=result['params']['repo_url'],
|
||||
defaults={
|
||||
'password': result['params']['password'],
|
||||
# 'encryption': result['params'].get('encryption', '')
|
||||
'encryption': result['params'].get('encryption', 'none')
|
||||
}
|
||||
)
|
||||
if 'cache' in result['data']:
|
||||
|
@ -117,7 +102,9 @@ class RepoTab(RepoBase, RepoUI):
|
|||
new_repo.unique_csize = stats['unique_csize']
|
||||
new_repo.unique_size = stats['unique_size']
|
||||
new_repo.total_unique_chunks = stats['total_unique_chunks']
|
||||
if 'encryption' in result['data']:
|
||||
new_repo.encryption = result['data']['encryption']['mode']
|
||||
|
||||
new_repo.save()
|
||||
self.profile.repo = new_repo.id
|
||||
self.profile.save()
|
||||
|
@ -135,4 +122,4 @@ class RepoTab(RepoBase, RepoUI):
|
|||
new_snapshot.save()
|
||||
self.repoSelector.addItem(new_repo.url, new_repo.id)
|
||||
self.repoSelector.setCurrentIndex(self.repoSelector.count()-1)
|
||||
self.init_snapshots()
|
||||
self.repo_changed.emit(self.profile.repo.id)
|
||||
|
|
|
@ -19,6 +19,12 @@ class SourceTab(SourceBase, SourceUI):
|
|||
for source in SourceDirModel.select():
|
||||
self.sourceDirectoriesWidget.addItem(source.dir)
|
||||
|
||||
self.excludePatternsField.appendPlainText(self.profile.exclude_patterns)
|
||||
self.excludeIfPresentField.appendPlainText(self.profile.exclude_if_present)
|
||||
|
||||
self.excludePatternsField.textChanged.connect(self.save_exclude_patterns)
|
||||
self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present)
|
||||
|
||||
def source_add(self):
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.ShowDirsOnly
|
||||
|
@ -35,3 +41,11 @@ class SourceTab(SourceBase, SourceUI):
|
|||
db_item = SourceDirModel.get(dir=item.text())
|
||||
db_item.delete_instance()
|
||||
item = None
|
||||
|
||||
def save_exclude_patterns(self):
|
||||
self.profile.exclude_patterns = self.excludePatternsField.toPlainText()
|
||||
self.profile.save()
|
||||
|
||||
def save_exclude_if_present(self):
|
||||
self.profile.exclude_if_present = self.excludeIfPresentField.toPlainText()
|
||||
self.profile.save()
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtWidgets import QMenu, QAction, QApplication, QSystemTrayIcon
|
||||
from vorta.main_window import MainWindow
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
from .utils import get_relative_asset
|
||||
from .config import remove_config
|
||||
from .borg_runner import BorgThread
|
||||
|
||||
class TrayMenu(QSystemTrayIcon):
|
||||
def __init__(self, parent=None):
|
||||
icon = QIcon(get_relative_asset('UI/icons/hdd-o.png'))
|
||||
QSystemTrayIcon.__init__(self, icon, parent)
|
||||
|
||||
self.app = parent
|
||||
menu = QMenu()
|
||||
|
||||
self.status = menu.addAction("Sleeping")
|
||||
self.status.setEnabled(False)
|
||||
|
||||
self.create_action = menu.addAction("Backup Now")
|
||||
self.create_action.triggered.connect(self.on_create_backup)
|
||||
|
||||
settings_action = menu.addAction("Settings")
|
||||
# settings_action.setEnabled(False)
|
||||
# settings_action.setText('In Progress')
|
||||
settings_action.setIcon(icon)
|
||||
settings_action.triggered.connect(self.on_settings_action)
|
||||
|
||||
menu.addSeparator()
|
||||
|
@ -24,6 +31,8 @@ class TrayMenu(QSystemTrayIcon):
|
|||
exit_action = menu.addAction("Exit")
|
||||
exit_action.triggered.connect(self.on_exit_action)
|
||||
|
||||
self.activated.connect(self.on_user_click)
|
||||
|
||||
self.setContextMenu(menu)
|
||||
self.setVisible(True)
|
||||
self.show()
|
||||
|
@ -38,3 +47,26 @@ class TrayMenu(QSystemTrayIcon):
|
|||
def on_reset(self):
|
||||
remove_config()
|
||||
QApplication.instance().quit()
|
||||
|
||||
def on_create_backup(self):
|
||||
thread_msg = BorgThread.create_thread_factory()
|
||||
if thread_msg['ok']:
|
||||
thread_msg['thread'].start()
|
||||
else:
|
||||
error_dialog = QtWidgets.QErrorMessage()
|
||||
error_dialog.showMessage(thread_msg['message'])
|
||||
error_dialog.show()
|
||||
|
||||
def on_cancel_backup(self):
|
||||
if self.app.thread and self.app.thread.isRunning():
|
||||
self.app.thread.terminate()
|
||||
|
||||
def on_user_click(self):
|
||||
if self.app.thread and self.app.thread.isRunning():
|
||||
self.status.setText('Backup in Progress')
|
||||
self.create_action.setText('Cancel Backup')
|
||||
self.create_action.triggered.connect(self.on_cancel_backup)
|
||||
else:
|
||||
self.status.setText('Sleeping')
|
||||
self.create_action.setText('Backup Now')
|
||||
self.create_action.triggered.connect(self.on_create_backup)
|
||||
|
|
Loading…
Add table
Reference in a new issue