mirror of
https://github.com/borgbase/vorta
synced 2025-03-08 21:05:56 +00:00
v0.5.2 Bugfixes and new Misc Settings Tab (#71)
* Fix uneven vspace. Fixes #67 * Add Python 3.7 to Travis. Use tox to test multiple Python versions. Fixes #72 * Add command line option to avoid forking and open main window while debugging. Fixes #73 * Use slug of profile name as archive prefix. Fixes #46 * Add settings tab. Add light system tray icon option. Fixes #56 and #74 * Incorporate review by @ThomasWaldmann
This commit is contained in:
parent
d84d158541
commit
30c6549f0f
26 changed files with 452 additions and 209 deletions
22
.travis.yml
22
.travis.yml
|
@ -33,7 +33,8 @@ dist: trusty
|
|||
env:
|
||||
global:
|
||||
- SETUP_XVFB=true
|
||||
- PYTHON=3.6.3
|
||||
- PYTHON36=3.6.3
|
||||
- PYTHON37=3.7.1
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
@ -48,19 +49,20 @@ matrix:
|
|||
install:
|
||||
- |
|
||||
if [ $TRAVIS_OS_NAME = "linux" ]; then
|
||||
#curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
#git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
|
||||
#git clone git://github.com/pyenv/pyenv-update.git $(pyenv root)/plugins/pyenv-update
|
||||
#export PATH="/home/travis/.pyenv/shims:${PATH}"
|
||||
export DISPLAY=:99.0
|
||||
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset
|
||||
sleep 3
|
||||
# cd /opt/pyenv && git pull origin master
|
||||
pyenv install -s $PYTHON36
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON36
|
||||
elif [ $TRAVIS_OS_NAME = "osx" ]; then
|
||||
brew upgrade pyenv
|
||||
pyenv install -s $PYTHON37
|
||||
pyenv install -s $PYTHON36
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON36 $PYTHON37
|
||||
fi
|
||||
pyenv install -s $PYTHON
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON
|
||||
|
||||
- pip install -U setuptools pip
|
||||
- pip install .
|
||||
|
@ -69,12 +71,10 @@ install:
|
|||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME = "linux" ]; then (herbstluftwm )& fi
|
||||
- if [ $TRAVIS_OS_NAME = "osx" ]; then (sudo Xvfb :99 -ac -screen 0 1024x768x8 )& fi
|
||||
- sleep 3
|
||||
|
||||
script:
|
||||
- pytest --forked
|
||||
- if [ $TRAVIS_OS_NAME = "linux" ]; then tox -e flake8; fi
|
||||
- tox
|
||||
|
||||
#after_script:
|
||||
#- |
|
||||
|
|
|
@ -68,7 +68,8 @@ max-line-length = 120
|
|||
exclude = build,dist,.git,.idea,.cache,.tox,.eggs
|
||||
|
||||
[tox:tox]
|
||||
envlist = py36,flake8
|
||||
envlist = py36,py37,flake8
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
@ -78,6 +79,7 @@ deps =
|
|||
pytest-xdist
|
||||
pytest-faulthandler
|
||||
commands=pytest
|
||||
passenv = DISPLAY
|
||||
|
||||
[testenv:flake8]
|
||||
deps =
|
||||
|
|
|
@ -8,9 +8,22 @@ from vorta.config import SETTINGS_DIR
|
|||
from vorta.updater import get_updater
|
||||
import vorta.sentry
|
||||
import vorta.log
|
||||
from vorta.utils import parse_args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
frozen_binary = getattr(sys, 'frozen', False)
|
||||
|
||||
# Don't fork if user specifies it or when running from onedir app bundle on macOS.
|
||||
if args.foreground or (frozen_binary and sys.platform == 'darwin'):
|
||||
pass
|
||||
else:
|
||||
print('Forking to background (see system tray).')
|
||||
if os.fork():
|
||||
sys.exit()
|
||||
|
||||
# Send crashes to Sentry.
|
||||
if not os.environ.get('NO_SENTRY', False):
|
||||
vorta.sentry.init()
|
||||
|
@ -21,6 +34,7 @@ def main():
|
|||
|
||||
app = VortaApp(sys.argv, single_app=True)
|
||||
app.updater = get_updater()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
|
|
|
@ -4,14 +4,13 @@ import fcntl
|
|||
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
from .tray_menu import TrayMenu
|
||||
from .scheduler import VortaScheduler
|
||||
from .models import BackupProfileModel
|
||||
from .borg.create import BorgCreateThread
|
||||
from .views.main_window import MainWindow
|
||||
from .utils import get_asset
|
||||
from .utils import parse_args, set_tray_icon
|
||||
from vorta.config import SETTINGS_DIR
|
||||
|
||||
|
||||
|
@ -28,7 +27,7 @@ class VortaApp(QApplication):
|
|||
backup_cancelled_event = QtCore.pyqtSignal()
|
||||
backup_log_event = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, args, single_app=False):
|
||||
def __init__(self, args_raw, single_app=False):
|
||||
|
||||
# Ensure only one app instance is running.
|
||||
# From https://stackoverflow.com/questions/220525/
|
||||
|
@ -43,14 +42,17 @@ class VortaApp(QApplication):
|
|||
print('An instance of Vorta is already running.')
|
||||
sys.exit(1)
|
||||
|
||||
super().__init__(args)
|
||||
super().__init__(args_raw)
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
self.scheduler = VortaScheduler(self)
|
||||
|
||||
# Prepare tray and main window
|
||||
self.tray = TrayMenu(self)
|
||||
self.main_window = MainWindow(self)
|
||||
# self.main_window.show()
|
||||
|
||||
args = parse_args()
|
||||
if args.foreground:
|
||||
self.main_window.show()
|
||||
|
||||
self.backup_started_event.connect(self.backup_started_event_response)
|
||||
self.backup_finished_event.connect(self.backup_finished_event_response)
|
||||
|
@ -73,14 +75,11 @@ class VortaApp(QApplication):
|
|||
self.main_window.raise_()
|
||||
|
||||
def backup_started_event_response(self):
|
||||
icon = QIcon(get_asset('icons/hdd-o-active.png'))
|
||||
self.tray.setIcon(icon)
|
||||
set_tray_icon(self.tray, active=True)
|
||||
|
||||
def backup_finished_event_response(self):
|
||||
icon = QIcon(get_asset('icons/hdd-o.png'))
|
||||
self.tray.setIcon(icon)
|
||||
set_tray_icon(self.tray)
|
||||
self.main_window.scheduleTab._draw_next_scheduled_backup()
|
||||
|
||||
def backup_cancelled_event_response(self):
|
||||
icon = QIcon(get_asset('icons/hdd-o.png'))
|
||||
self.tray.setIcon(icon)
|
||||
set_tray_icon(self.tray)
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
|
@ -92,7 +92,7 @@
|
|||
<item>
|
||||
<widget class="QToolButton" name="profileRenameButton">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
|
@ -109,7 +109,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<normaloff>:/icons/trash.svg</normaloff>:/icons/trash.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -132,7 +132,7 @@
|
|||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>false</bool>
|
||||
|
@ -166,6 +166,11 @@
|
|||
<string>Archives</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="miscTabSlot">
|
||||
<attribute name="title">
|
||||
<string>Misc</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -181,7 +186,7 @@
|
|||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>45</height>
|
||||
<height>55</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
|
|
75
src/vorta/assets/UI/misctab.ui
Normal file
75
src/vorta/assets/UI/misctab.ui
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>791</width>
|
||||
<height>497</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="checkboxLayout">
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Version:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -17,6 +17,19 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<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>
|
||||
<item row="1" column="0">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
|
@ -139,24 +152,6 @@
|
|||
</item>
|
||||
</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>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLabel" name="errorText">
|
||||
<property name="sizePolicy">
|
||||
|
@ -193,21 +188,26 @@
|
|||
</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>
|
||||
|
|
|
@ -24,39 +24,108 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure your backup repository (you can add a new or existing repository). For remote repositories, you will need a SSH key to log in without a password (if you already have a key, just keep it at the default).</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>25</number>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QComboBox" name="repoCompression"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-bottom: 10</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Remote or local backup repository. For secure remote backups, try <a href="https://www.borgbase.com/?utm_source=vorta&utm_medium=app"><span style=" text-decoration: underline; color:#0000ff;">BorgBase</span></a>. 100GB free during Beta.</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-bottom: 10</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>To securely access remote repositories. Keep default to use all your existing keys. Or create new key.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Repository:</string>
|
||||
|
@ -64,10 +133,46 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="sshComboBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="sshKeyToClipboardButton">
|
||||
<property name="toolTip">
|
||||
<string>Copy public SSH key to clipboard.</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/copy.svg</normaloff>:/icons/copy.svg</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="arrowType">
|
||||
<enum>Qt::NoArrow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="repoSelector">
|
||||
<property name="sizePolicy">
|
||||
|
@ -102,97 +207,28 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-bottom: 10</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Remote or local backup repository. For secure remote backups, try <a href="https://www.borgbase.com/?utm_source=vorta&utm_medium=app"><span style=" text-decoration: underline; color:#0000ff;">BorgBase</span></a>. 100GB free during Beta.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="repoCompression"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>SSH Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="sshComboBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="sshKeyToClipboardButton">
|
||||
<property name="toolTip">
|
||||
<string>Copy public SSH key to clipboard.</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/copy.svg</normaloff>:/icons/copy.svg</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="arrowType">
|
||||
<enum>Qt::NoArrow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-bottom: 10</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>To securely access remote repositories. Keep default to use all your existing keys. Or create new key.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="repoStats">
|
||||
<property name="fieldGrowthPolicy">
|
||||
|
|
|
@ -29,7 +29,7 @@ font-weight: bold;
|
|||
}</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="schedule">
|
||||
<property name="geometry">
|
||||
|
@ -353,7 +353,7 @@ font-weight: bold;
|
|||
<widget class="QListWidget" name="wifiListWidget"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<widget class="QLabel" name="wifiListLabel">
|
||||
<property name="text">
|
||||
<string>Allowed Networks:</string>
|
||||
</property>
|
||||
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
src/vorta/assets/icons/hdd-o-active-light.png
Normal file
BIN
src/vorta/assets/icons/hdd-o-active-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
src/vorta/assets/icons/hdd-o-light.png
Normal file
BIN
src/vorta/assets/icons/hdd-o-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -103,7 +103,7 @@ class BorgCreateThread(BorgThread):
|
|||
cmd.extend(['--exclude-if-present', f.strip()])
|
||||
|
||||
# Add repo url and source dirs.
|
||||
cmd.append(f"{profile.repo.url}::{platform.node()}-{profile.id}-{dt.now().isoformat(timespec='seconds')}")
|
||||
cmd.append(f"{profile.repo.url}::{platform.node()}-{profile.slug()}-{dt.now().isoformat(timespec='seconds')}")
|
||||
|
||||
for f in SourceDirModel.select().where(SourceDirModel.profile == profile.id):
|
||||
cmd.append(f.dir)
|
||||
|
|
|
@ -32,7 +32,7 @@ class BorgPruneThread(BorgThread):
|
|||
'--keep-weekly', str(profile.prune_week),
|
||||
'--keep-monthly', str(profile.prune_month),
|
||||
'--keep-yearly', str(profile.prune_year),
|
||||
'--prefix', f'{platform.node()}-'
|
||||
'--prefix', f'{platform.node()}-{profile.slug()}'
|
||||
]
|
||||
if profile.prune_keep_within:
|
||||
pruning_opts += ['--keep-within', profile.prune_keep_within]
|
||||
|
|
|
@ -4,10 +4,12 @@ This module provides the app's data store using Peewee with SQLite.
|
|||
At the bottom there is a simple schema migration system.
|
||||
"""
|
||||
|
||||
import peewee as pw
|
||||
import sys
|
||||
import json
|
||||
import peewee as pw
|
||||
from datetime import datetime, timedelta
|
||||
from playhouse.migrate import SqliteMigrator, migrate
|
||||
from vorta.utils import slugify
|
||||
|
||||
SCHEMA_VERSION = 8
|
||||
|
||||
|
@ -82,6 +84,9 @@ class BackupProfileModel(pw.Model):
|
|||
def refresh(self):
|
||||
return type(self).get(self._pk_expr())
|
||||
|
||||
def slug(self):
|
||||
return slugify(self.name)
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
|
@ -148,16 +153,21 @@ class SchemaVersion(pw.Model):
|
|||
database = db
|
||||
|
||||
|
||||
class SettingsModel(pw.Model):
|
||||
"""App settings unrelated to a single profile or repo"""
|
||||
key = pw.CharField(unique=True)
|
||||
value = pw.BooleanField()
|
||||
label = pw.CharField()
|
||||
type = pw.CharField()
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
|
||||
class BackupProfileMixin:
|
||||
"""Extend to support multiple profiles later."""
|
||||
def profile(self):
|
||||
return BackupProfileModel.get(id=self.window().current_profile.id)
|
||||
# app = QApplication.instance()
|
||||
# main_window = hasattr(app, 'main_window')
|
||||
# if main_window:
|
||||
# return app.main_window.current_profile
|
||||
# else:
|
||||
# return BackupProfileModel.select().first()
|
||||
|
||||
|
||||
def _apply_schema_update(current_schema, version_after, *operations):
|
||||
|
@ -171,13 +181,33 @@ def _apply_schema_update(current_schema, version_after, *operations):
|
|||
def init_db(con):
|
||||
db.initialize(con)
|
||||
db.connect()
|
||||
db.create_tables([RepoModel, RepoPassword, BackupProfileModel, SourceDirModel,
|
||||
db.create_tables([RepoModel, RepoPassword, BackupProfileModel, SourceDirModel, SettingsModel,
|
||||
ArchiveModel, WifiSettingModel, EventLogModel, SchemaVersion])
|
||||
|
||||
if BackupProfileModel.select().count() == 0:
|
||||
default_profile = BackupProfileModel(name='Default Profile')
|
||||
default_profile.save()
|
||||
|
||||
# Default settings
|
||||
settings = [
|
||||
{'key': 'use_light_icon', 'value': False, 'type': 'checkbox',
|
||||
'label': 'Use light system tray icon (applies after restart, useful for dark themes).'}
|
||||
]
|
||||
if sys.platform == 'darwin':
|
||||
settings += [
|
||||
{'key': 'autostart', 'value': False, 'type': 'checkbox',
|
||||
'label': 'Add Vorta to Login Items in Preferences > Users and Groups > Login Items.'},
|
||||
{'key': 'enable_notifications', 'value': True, 'type': 'checkbox',
|
||||
'label': 'Display notifications when background tasks fail.'},
|
||||
{'key': 'check_for_updates', 'value': True, 'type': 'checkbox',
|
||||
'label': 'Check for updates on startup.'},
|
||||
]
|
||||
|
||||
for setting in settings: # Create missing settings and update labels.
|
||||
s, created = SettingsModel.get_or_create(key=setting['key'], defaults=setting)
|
||||
s.label = setting['label']
|
||||
s.save()
|
||||
|
||||
# Delete old log entries after 3 months.
|
||||
three_months_ago = datetime.now() - timedelta(days=180)
|
||||
EventLogModel.delete().where(EventLogModel.start_time < three_months_ago)
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
from PyQt5.QtWidgets import QMenu, QSystemTrayIcon
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
from .utils import get_asset
|
||||
from .borg.borg_thread import BorgThread
|
||||
from .models import BackupProfileModel
|
||||
from .utils import set_tray_icon
|
||||
|
||||
|
||||
class TrayMenu(QSystemTrayIcon):
|
||||
def __init__(self, parent=None):
|
||||
icon = QIcon(get_asset('icons/hdd-o.png'))
|
||||
QSystemTrayIcon.__init__(self, icon, parent)
|
||||
QSystemTrayIcon.__init__(self, parent)
|
||||
self.app = parent
|
||||
set_tray_icon(self)
|
||||
menu = QMenu()
|
||||
|
||||
# Workaround to get `activated` signal on Unity: https://stackoverflow.com/a/43683895/3983708
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import os
|
||||
import sys
|
||||
import plistlib
|
||||
import argparse
|
||||
import unicodedata
|
||||
import re
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import reduce
|
||||
|
@ -11,10 +14,10 @@ from paramiko.ecdsakey import ECDSAKey
|
|||
from paramiko.ed25519key import Ed25519Key
|
||||
from paramiko import SSHException
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5 import QtCore
|
||||
import subprocess
|
||||
import keyring
|
||||
from .models import WifiSettingModel
|
||||
|
||||
|
||||
class VortaKeyring(keyring.backend.KeyringBackend):
|
||||
|
@ -140,6 +143,8 @@ def get_asset(path):
|
|||
def get_sorted_wifis(profile):
|
||||
"""Get SSIDs from OS and merge with settings in DB."""
|
||||
|
||||
from vorta.models import WifiSettingModel
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
plist_path = '/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist'
|
||||
plist_file = open(plist_path, 'rb')
|
||||
|
@ -185,3 +190,32 @@ def get_current_wifi():
|
|||
split_line = line.strip().split(':')
|
||||
if split_line[0] == 'SSID':
|
||||
return split_line[1].strip()
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Vorta Backup GUI for Borg.')
|
||||
parser.add_argument('--foreground', '-f',
|
||||
action='store_true',
|
||||
help="Don't fork into background and open main window on startup.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def slugify(value):
|
||||
"""
|
||||
Converts to lowercase, removes non-word characters (alphanumerics and
|
||||
underscores) and converts spaces to hyphens. Also strips leading and
|
||||
trailing whitespace.
|
||||
|
||||
Copied from Django.
|
||||
"""
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
||||
value = re.sub(r'[^\w\s-]', '', value).strip().lower()
|
||||
return re.sub(r'[-\s]+', '-', value)
|
||||
|
||||
|
||||
def set_tray_icon(tray, active=False):
|
||||
from vorta.models import SettingsModel
|
||||
use_light_style = SettingsModel.get(key='use_light_icon').value
|
||||
icon_name = f"icons/hdd-o{'-active' if active else ''}-{'light' if use_light_style else 'dark'}.png"
|
||||
icon = QIcon(get_asset(icon_name))
|
||||
tray.setIcon(icon)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import sys
|
||||
from PyQt5.QtWidgets import QShortcut
|
||||
from PyQt5 import uic, QtCore
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
|
@ -6,6 +7,7 @@ from .repo_tab import RepoTab
|
|||
from .source_tab import SourceTab
|
||||
from .archive_tab import ArchiveTab
|
||||
from .schedule_tab import ScheduleTab
|
||||
from .misc_tab import MiscTab
|
||||
from .profile_add_edit_dialog import AddProfileWindow, EditProfileWindow
|
||||
from ..utils import get_asset
|
||||
from ..models import BackupProfileModel
|
||||
|
@ -30,6 +32,7 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.sourceTab = SourceTab(self.sourceTabSlot)
|
||||
self.archiveTab = ArchiveTab(self.archiveTabSlot)
|
||||
self.scheduleTab = ScheduleTab(self.scheduleTabSlot)
|
||||
self.miscTabSlot = MiscTab(self.miscTabSlot)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
|
||||
self.repoTab.repo_changed.connect(self.archiveTab.populate_from_profile)
|
||||
|
@ -56,6 +59,14 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.profileRenameButton.clicked.connect(self.profile_rename_action)
|
||||
self.profileDeleteButton.clicked.connect(self.profile_delete_action)
|
||||
|
||||
# OS-specific startup options:
|
||||
if sys.platform != 'darwin':
|
||||
# Hide Wifi-rule section in schedule tab.
|
||||
self.scheduleTab.wifiListLabel.hide()
|
||||
self.scheduleTab.wifiListWidget.hide()
|
||||
self.scheduleTab.page_2.hide()
|
||||
self.scheduleTab.toolBox.removeItem(1)
|
||||
|
||||
# Connect to existing thread.
|
||||
if BorgThread.is_running():
|
||||
self.createStartBtn.setEnabled(False)
|
||||
|
|
28
src/vorta/views/misc_tab.py
Normal file
28
src/vorta/views/misc_tab.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from PyQt5 import uic
|
||||
from PyQt5.QtWidgets import QCheckBox
|
||||
from vorta.utils import get_asset
|
||||
from vorta.models import SettingsModel
|
||||
from vorta._version import __version__
|
||||
|
||||
uifile = get_asset('UI/misctab.ui')
|
||||
MiscTabUI, MiscTabBase = uic.loadUiType(uifile, from_imports=True, import_from='vorta.views')
|
||||
|
||||
|
||||
class MiscTab(MiscTabBase, MiscTabUI):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setupUi(parent)
|
||||
self.versionLabel.setText(__version__)
|
||||
|
||||
for setting in SettingsModel.select().where(SettingsModel.type == 'checkbox'):
|
||||
b = QCheckBox(setting.label)
|
||||
b.setCheckState(setting.value)
|
||||
b.setTristate(False)
|
||||
b.stateChanged.connect(lambda v, key=setting.key: self.save_setting(key, v))
|
||||
self.checkboxLayout.addWidget(b)
|
||||
|
||||
def save_setting(self, key, new_value):
|
||||
setting = SettingsModel.get(key=key)
|
||||
setting.value = bool(new_value)
|
||||
setting.save()
|
|
@ -34,7 +34,7 @@ class AddProfileWindow(AddProfileBase, AddProfileUI):
|
|||
|
||||
def validate(self):
|
||||
name = self.profileNameField.text()
|
||||
# Name as entered?
|
||||
# A name was entered?
|
||||
if len(name) == 0:
|
||||
self._set_status('Please enter a profile name.')
|
||||
return False
|
||||
|
|
|
@ -45,7 +45,6 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin):
|
|||
|
||||
item = "directory" if want_folder else "file"
|
||||
dialog = choose_folder_dialog(self, "Choose %s to back up" % item, want_folder=want_folder)
|
||||
self._file_dialog = dialog # for pytest
|
||||
dialog.open(receive)
|
||||
|
||||
def source_remove(self):
|
||||
|
|
|
@ -28,10 +28,11 @@ def app(tmpdir, qtbot):
|
|||
test_archive = ArchiveModel(snapshot_id='99999', name='test-archive', time=dt(2000, 1, 1, 0, 0), repo=1)
|
||||
test_archive.save()
|
||||
|
||||
source_dir = SourceDirModel(dir='/tmp', repo=new_repo)
|
||||
source_dir = SourceDirModel(dir='/tmp/another', repo=new_repo)
|
||||
source_dir.save()
|
||||
|
||||
app = VortaApp([])
|
||||
app.main_window.show()
|
||||
qtbot.addWidget(app.main_window)
|
||||
return app
|
||||
|
||||
|
@ -39,7 +40,7 @@ def app(tmpdir, qtbot):
|
|||
@pytest.fixture
|
||||
def choose_folder_dialog(*args):
|
||||
class MockFileDialog:
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def open(self, func):
|
||||
|
|
|
@ -98,7 +98,7 @@ def test_archive_mount(app, qtbot, mocker, borg_json_output, monkeypatch, choose
|
|||
)
|
||||
|
||||
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
|
||||
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Mounted'), timeout=1000)
|
||||
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Mounted'), timeout=5000)
|
||||
|
||||
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
|
||||
# qtbot.waitUntil(lambda: tab.mountErrors.text() == 'No active Borg mounts found.')
|
||||
|
@ -127,7 +127,7 @@ def test_archive_extract(app, qtbot, mocker, borg_json_output, monkeypatch):
|
|||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
qtbot.mouseClick(tab.extractButton, QtCore.Qt.LeftButton)
|
||||
|
||||
qtbot.waitUntil(lambda: hasattr(tab, '_window'))
|
||||
qtbot.waitUntil(lambda: hasattr(tab, '_window'), timeout=5000)
|
||||
|
||||
assert tab._window.treeView.model().rootItem.childItems[0].data(0) == 'Users'
|
||||
tab._window.treeView.model().rootItem.childItems[0].load_children()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import uuid
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
import vorta.borg.borg_thread
|
||||
import vorta.models
|
||||
|
@ -9,10 +10,12 @@ from vorta.views.ssh_dialog import SSHAddWindow
|
|||
from vorta.models import EventLogModel, RepoModel, ArchiveModel
|
||||
|
||||
|
||||
def test_repo_add(app, qtbot, mocker, borg_json_output):
|
||||
def test_repo_add_failures(app, qtbot, mocker, borg_json_output):
|
||||
# Add new repo window
|
||||
main = app.main_window
|
||||
add_repo_window = AddRepoWindow(main.repoTab)
|
||||
add_repo_window = AddRepoWindow(main)
|
||||
qtbot.addWidget(add_repo_window)
|
||||
|
||||
qtbot.keyClicks(add_repo_window.repoURL, 'aaa')
|
||||
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
|
||||
assert add_repo_window.errorText.text().startswith('Please enter a valid')
|
||||
|
@ -21,6 +24,15 @@ def test_repo_add(app, qtbot, mocker, borg_json_output):
|
|||
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
|
||||
assert add_repo_window.errorText.text() == 'Please use a longer password.'
|
||||
|
||||
|
||||
def test_repo_add_success(app, qtbot, mocker, borg_json_output):
|
||||
# Add new repo window
|
||||
main = app.main_window
|
||||
add_repo_window = AddRepoWindow(main)
|
||||
qtbot.addWidget(add_repo_window)
|
||||
test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo' # Random repo URL to avoid macOS keychain
|
||||
|
||||
qtbot.keyClicks(add_repo_window.repoURL, test_repo_url)
|
||||
qtbot.keyClicks(add_repo_window.passwordLineEdit, 'long-password-long')
|
||||
|
||||
stdout, stderr = borg_json_output('info')
|
||||
|
@ -34,8 +46,9 @@ def test_repo_add(app, qtbot, mocker, borg_json_output):
|
|||
|
||||
main.repoTab.process_new_repo(blocker.args[0])
|
||||
|
||||
# assert EventLogModel.select().count() == 2
|
||||
assert RepoModel.get(id=2).url == 'aaabbb.com:repo'
|
||||
qtbot.waitUntil(lambda: EventLogModel.select().count() == 2)
|
||||
assert EventLogModel.select().count() == 2
|
||||
assert RepoModel.get(id=2).url == test_repo_url
|
||||
|
||||
|
||||
def test_repo_unlink(app, qtbot, monkeypatch):
|
||||
|
@ -70,9 +83,6 @@ def test_ssh_dialog(qtbot, tmpdir):
|
|||
assert pub_tmpfile_content.startswith('ssh-ed25519')
|
||||
qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('New key was copied'))
|
||||
|
||||
clipboard = QApplication.clipboard()
|
||||
assert clipboard.text().startswith('ssh-ed25519')
|
||||
|
||||
qtbot.mouseClick(ssh_dialog.generateButton, QtCore.Qt.LeftButton)
|
||||
qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('Key file already'))
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import logging
|
||||
from PyQt5 import QtCore
|
||||
import vorta.models
|
||||
import vorta.views
|
||||
|
||||
|
||||
def test_add_folder(app, qtbot, tmpdir):
|
||||
def test_add_folder(app, qtbot, tmpdir, monkeypatch, choose_folder_dialog):
|
||||
monkeypatch.setattr(
|
||||
vorta.views.source_tab, "choose_folder_dialog", choose_folder_dialog
|
||||
)
|
||||
main = app.main_window
|
||||
main.tabWidget.setCurrentIndex(1)
|
||||
tab = main.sourceTab
|
||||
|
||||
qtbot.mouseClick(tab.sourceAddFolder, QtCore.Qt.LeftButton)
|
||||
|
||||
qtbot.waitUntil(lambda: len(tab._file_dialog.selectedFiles()) > 0, timeout=3000)
|
||||
tab._file_dialog.accept()
|
||||
|
||||
qtbot.waitUntil(lambda: tab.sourceDirectoriesWidget.count() == 2)
|
||||
|
||||
for src in vorta.models.SourceDirModel.select():
|
||||
|
|
Loading…
Add table
Reference in a new issue