diff --git a/.travis.yml b/.travis.yml
index ce6f446a..eb5c4dba 100644
--- a/.travis.yml
+++ b/.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:
#- |
diff --git a/setup.cfg b/setup.cfg
index 729b4d7c..0c777a43 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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 =
diff --git a/src/vorta/__main__.py b/src/vorta/__main__.py
index a4fc1696..72d07bfe 100644
--- a/src/vorta/__main__.py
+++ b/src/vorta/__main__.py
@@ -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_())
diff --git a/src/vorta/application.py b/src/vorta/application.py
index 0cb5d420..b19c2ff6 100644
--- a/src/vorta/application.py
+++ b/src/vorta/application.py
@@ -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)
diff --git a/src/vorta/assets/UI/mainwindow.ui b/src/vorta/assets/UI/mainwindow.ui
index fb3c7564..4df3a95b 100644
--- a/src/vorta/assets/UI/mainwindow.ui
+++ b/src/vorta/assets/UI/mainwindow.ui
@@ -54,7 +54,7 @@
-
- 5
+ 12
0
@@ -92,7 +92,7 @@
-
-
+
:/icons/edit.svg:/icons/edit.svg
@@ -109,7 +109,7 @@
...
-
+
:/icons/trash.svg:/icons/trash.svg
@@ -132,7 +132,7 @@
-
- 3
+ 4
false
@@ -166,6 +166,11 @@
Archives
+
+
+ Misc
+
+
-
@@ -181,7 +186,7 @@
0
- 45
+ 55
diff --git a/src/vorta/assets/UI/misctab.ui b/src/vorta/assets/UI/misctab.ui
new file mode 100644
index 00000000..e83988e1
--- /dev/null
+++ b/src/vorta/assets/UI/misctab.ui
@@ -0,0 +1,75 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 791
+ 497
+
+
+
+ Form
+
+
+
-
+
+
+ 10
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Version:
+
+
+
+ -
+
+
+ 0.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+
+
+
+
diff --git a/src/vorta/assets/UI/repoadd.ui b/src/vorta/assets/UI/repoadd.ui
index 4fcbf439..cd03369b 100644
--- a/src/vorta/assets/UI/repoadd.ui
+++ b/src/vorta/assets/UI/repoadd.ui
@@ -17,6 +17,19 @@
true
+ -
+
+
+
+ 75
+ true
+
+
+
+ Initialize New Backup Repository
+
+
+
-
@@ -139,24 +152,6 @@
- -
-
-
-
-
-
- Add
-
-
-
- -
-
-
- Cancel
-
-
-
-
-
-
@@ -193,21 +188,26 @@
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
- -
-
-
-
- 75
- true
-
-
-
- Initialize New Backup Repository
-
-
-
diff --git a/src/vorta/assets/UI/repotab.ui b/src/vorta/assets/UI/repotab.ui
index 2cb6d50c..631c9656 100644
--- a/src/vorta/assets/UI/repotab.ui
+++ b/src/vorta/assets/UI/repotab.ui
@@ -24,39 +24,108 @@
-
-
-
-
- 0
- 0
-
-
-
- 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).
-
-
- true
-
-
-
- -
-
-
- QFormLayout::AllNonFixedFieldsGrow
-
-
- Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
-
-
- 10
-
+
- 5
+ 0
-
- 25
-
-
-
+
-
+
+
-
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 12
+
+
+
+ margin-bottom: 10
+
+
+ <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>
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 12
+
+
+
+ margin-bottom: 10
+
+
+ To securely access remote repositories. Keep default to use all your existing keys. Or create new key.
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+ -
+
+
+ Compression:
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
Repository:
@@ -64,10 +133,46 @@
-
+
+
+ 0
+
+
-
+
+
+ -
+
+
+ Copy public SSH key to clipboard.
+
+
+
+
+
+ Copy
+
+
+
+ :/icons/copy.svg:/icons/copy.svg
+
+
+ false
+
+
+ Qt::NoArrow
+
+
+
+
+
+ -
0
+
+ 0
+
-
@@ -102,97 +207,28 @@
- -
-
-
-
- 12
-
-
-
- margin-bottom: 10
-
-
- <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>
-
-
- true
-
-
- true
-
-
-
- -
-
-
- Compression:
-
-
-
- -
-
-
- -
+
-
SSH Key:
- -
-
-
- 0
-
-
-
-
-
- -
-
-
- Copy public SSH key to clipboard.
-
-
-
-
-
- Copy
-
-
-
- :/icons/copy.svg:/icons/copy.svg
-
-
- false
-
-
- Qt::NoArrow
-
-
-
-
-
- -
-
-
-
- 12
-
-
-
- margin-bottom: 10
-
-
- To securely access remote repositories. Keep default to use all your existing keys. Or create new key.
-
-
- true
-
-
-
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
-
diff --git a/src/vorta/assets/UI/scheduletab.ui b/src/vorta/assets/UI/scheduletab.ui
index 2eb1c905..9bf53f1a 100644
--- a/src/vorta/assets/UI/scheduletab.ui
+++ b/src/vorta/assets/UI/scheduletab.ui
@@ -29,7 +29,7 @@ font-weight: bold;
}
- 2
+ 1
@@ -353,7 +353,7 @@ font-weight: bold;
-
-
+
Allowed Networks:
diff --git a/src/vorta/assets/icons/hdd-o-active.png b/src/vorta/assets/icons/hdd-o-active-dark.png
similarity index 100%
rename from src/vorta/assets/icons/hdd-o-active.png
rename to src/vorta/assets/icons/hdd-o-active-dark.png
diff --git a/src/vorta/assets/icons/hdd-o-active-light.png b/src/vorta/assets/icons/hdd-o-active-light.png
new file mode 100644
index 00000000..bc6fb3bf
Binary files /dev/null and b/src/vorta/assets/icons/hdd-o-active-light.png differ
diff --git a/src/vorta/assets/icons/hdd-o.png b/src/vorta/assets/icons/hdd-o-dark.png
similarity index 100%
rename from src/vorta/assets/icons/hdd-o.png
rename to src/vorta/assets/icons/hdd-o-dark.png
diff --git a/src/vorta/assets/icons/hdd-o-light.png b/src/vorta/assets/icons/hdd-o-light.png
new file mode 100644
index 00000000..0a80c621
Binary files /dev/null and b/src/vorta/assets/icons/hdd-o-light.png differ
diff --git a/src/vorta/borg/create.py b/src/vorta/borg/create.py
index e6a22402..17f8f33e 100644
--- a/src/vorta/borg/create.py
+++ b/src/vorta/borg/create.py
@@ -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)
diff --git a/src/vorta/borg/prune.py b/src/vorta/borg/prune.py
index 235b76a9..b79a56db 100644
--- a/src/vorta/borg/prune.py
+++ b/src/vorta/borg/prune.py
@@ -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]
diff --git a/src/vorta/models.py b/src/vorta/models.py
index 7914fd35..a261c608 100644
--- a/src/vorta/models.py
+++ b/src/vorta/models.py
@@ -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)
diff --git a/src/vorta/tray_menu.py b/src/vorta/tray_menu.py
index 5dcba6dd..7a41257d 100644
--- a/src/vorta/tray_menu.py
+++ b/src/vorta/tray_menu.py
@@ -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
diff --git a/src/vorta/utils.py b/src/vorta/utils.py
index 5c12c4dd..99c41e97 100644
--- a/src/vorta/utils.py
+++ b/src/vorta/utils.py
@@ -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)
diff --git a/src/vorta/views/main_window.py b/src/vorta/views/main_window.py
index 5f8c992e..f7afc0bc 100644
--- a/src/vorta/views/main_window.py
+++ b/src/vorta/views/main_window.py
@@ -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)
diff --git a/src/vorta/views/misc_tab.py b/src/vorta/views/misc_tab.py
new file mode 100644
index 00000000..39b28792
--- /dev/null
+++ b/src/vorta/views/misc_tab.py
@@ -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()
diff --git a/src/vorta/views/profile_add_edit_dialog.py b/src/vorta/views/profile_add_edit_dialog.py
index b50cdf8d..838a6d63 100644
--- a/src/vorta/views/profile_add_edit_dialog.py
+++ b/src/vorta/views/profile_add_edit_dialog.py
@@ -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
diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py
index 74ab77b4..104aa38e 100644
--- a/src/vorta/views/source_tab.py
+++ b/src/vorta/views/source_tab.py
@@ -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):
diff --git a/tests/conftest.py b/tests/conftest.py
index 99f94b39..ac28e80a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -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):
diff --git a/tests/test_archives.py b/tests/test_archives.py
index 8ee22d87..31993e29 100644
--- a/tests/test_archives.py
+++ b/tests/test_archives.py
@@ -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()
diff --git a/tests/test_repo.py b/tests/test_repo.py
index 944f6deb..a40fc494 100644
--- a/tests/test_repo.py
+++ b/tests/test_repo.py
@@ -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'))
diff --git a/tests/test_source.py b/tests/test_source.py
index ef39234b..6820525d 100644
--- a/tests/test_source.py
+++ b/tests/test_source.py
@@ -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():