mirror of
https://github.com/borgbase/vorta
synced 2025-03-15 08:29:42 +00:00
v0.5.1 (#63)
* Simplify non-blocking BorgThread.run. * Fix issue with displaying nested folders in extract-dialog. * Fix error text expansion. * Add many new tests. Dont open main window on startup.
This commit is contained in:
parent
74047e1dcb
commit
b55c32517c
27 changed files with 577 additions and 245 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,3 +14,4 @@ vorta.egg-info
|
|||
.python-version
|
||||
.vagrant
|
||||
*.log
|
||||
htmlcov
|
||||
|
|
2
Makefile
2
Makefile
|
@ -14,7 +14,7 @@ github-release: Vorta.dmg
|
|||
hub release create --prerelease --attach=dist/vorta-0.5.0.dmg v0.5.0
|
||||
git checkout gh-pages
|
||||
git commit -m 'rebuild pages' --allow-empty
|
||||
git push origin gh-pages
|
||||
git push upstream gh-pages
|
||||
git checkout master
|
||||
|
||||
pypi-release:
|
||||
|
|
11
README.md
11
README.md
|
@ -7,17 +7,12 @@ Vorta is an open source macOS/Linux GUI for [BorgBackup](https://borgbackup.read
|
|||
|
||||
## Main features
|
||||
|
||||
- Encrypted, deduplicated and compressed backups.
|
||||
- Encrypted, deduplicated and compressed backups using `borg` as battle-tested backend.
|
||||
- Works with any local or remote Borg repo. Try [BorgBase](https://www.borgbase.com) for advanced features like append-only repositories and monitoring.
|
||||
- Add SSH keys and initialize repos directly from the GUI.
|
||||
- Repo keys are securely stored in macOS Keychain, SecretService or KWallet.
|
||||
- Mount existing archives via FUSE.
|
||||
- Repo keys are securely stored in macOS Keychain, SecretService or KWallet. Create SSH keys directly from the GUI.
|
||||
- Manage multiple backup profiles with different source folders, destinations and settings.
|
||||
- Prune and check backups periodically.
|
||||
- Flexible scheduling for automatic background backups. Only allow on certain Wifis.
|
||||
- View a list of archives and action logs.
|
||||
- Exclude options/patterns.
|
||||
- Automatic updates using Sparkle (on macOS)
|
||||
- Restore files from mounts or using the built-in backup browser.
|
||||
|
||||
## Installation and Download
|
||||
Vorta should work on all platforms that support Qt and Borg. Currently Borg doesn't support Windows, but this may change in the future. Setting allowed Wifi networks is currently not supported on Linux, but everything else should work.
|
||||
|
|
|
@ -50,7 +50,7 @@ class VortaApp(QApplication):
|
|||
# Prepare tray and main window
|
||||
self.tray = TrayMenu(self)
|
||||
self.main_window = MainWindow(self)
|
||||
self.main_window.show()
|
||||
# self.main_window.show()
|
||||
|
||||
self.backup_started_event.connect(self.backup_started_event_response)
|
||||
self.backup_finished_event.connect(self.backup_finished_event_response)
|
||||
|
|
|
@ -257,7 +257,7 @@
|
|||
<string>Extract</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/cloud-download.svg</normaloff>:/icons/cloud-download.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -274,7 +274,7 @@
|
|||
<string>Mount</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder-open.svg</normaloff>:/icons/folder-open.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -285,7 +285,7 @@
|
|||
<string>Check</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/check-circle.svg</normaloff>:/icons/check-circle.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -309,7 +309,7 @@
|
|||
<string>Prune</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/cut.svg</normaloff>:/icons/cut.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -329,7 +329,7 @@
|
|||
<string>Refresh</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/refresh.svg</normaloff>:/icons/refresh.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<item>
|
||||
<widget class="QToolButton" name="profileRenameButton">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/edit.svg</normaloff>:/icons/edit.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
|
@ -109,7 +109,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/trash.svg</normaloff>:/icons/trash.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>555</width>
|
||||
<height>270</height>
|
||||
<height>277</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -159,6 +159,12 @@
|
|||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLabel" name="errorText">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
|
@ -170,9 +176,15 @@
|
|||
<pointsize>11</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/unlink.svg</normaloff>:/icons/unlink.svg</iconset>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
|
@ -160,7 +160,7 @@
|
|||
<string>Copy</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons/collection.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/copy.svg</normaloff>:/icons/copy.svg</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
|
|
|
@ -6,176 +6,179 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>450</width>
|
||||
<height>300</height>
|
||||
<width>557</width>
|
||||
<height>286</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>451</width>
|
||||
<height>281</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Generate SSH Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Key Format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="formatSelect">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Key Length:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="lengthSelect">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>2048 or 4096 for RSA, 384 or 521 for ECDSA. Fixed for Ed25519. <a href="https://stribika.github.io/2015/01/04/secure-secure-shell.html"><span style=" text-decoration: underline; color:#0000ff;">More</span></a>.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="outputFileTextBox"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Don't change this if you want SSH to automatically find the key.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item alignment="Qt::AlignBottom">
|
||||
<widget class="QLabel" name="errors">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="generateButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>2</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Generate and copy to Clipboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Generate SSH Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Key Format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="formatSelect">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Key Length:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="lengthSelect">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>2048 or 4096 for RSA, 384 or 521 for ECDSA. Fixed for Ed25519. <a href="https://stribika.github.io/2015/01/04/secure-secure-shell.html"><span style=" text-decoration: underline; color:#0000ff;">More</span></a>.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="outputFileTextBox"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Don't change this if you want SSH to automatically find the key.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="generateButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>2</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Generate and copy to Clipboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="errors">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -4,7 +4,6 @@ import sys
|
|||
import shutil
|
||||
import signal
|
||||
import select
|
||||
import fcntl
|
||||
import time
|
||||
import logging
|
||||
from PyQt5 import QtCore
|
||||
|
@ -145,26 +144,20 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
|
|||
env=self.env, cwd=self.cwd, preexec_fn=os.setsid)
|
||||
self.process = p
|
||||
|
||||
# Prevent blocking. via https://stackoverflow.com/a/7730201/3983708
|
||||
# Prevent blocking of stdout/err. Via https://stackoverflow.com/a/7730201/3983708
|
||||
os.set_blocking(p.stdout.fileno(), False)
|
||||
os.set_blocking(p.stderr.fileno(), False)
|
||||
|
||||
# Helper function to add the O_NONBLOCK flag to a file descriptor
|
||||
def make_async(fd):
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
|
||||
# Helper function to read some data from a file descriptor, ignoring EAGAIN errors
|
||||
def read_async(fd):
|
||||
try:
|
||||
return fd.read()
|
||||
except (IOError, TypeError):
|
||||
return ''
|
||||
|
||||
make_async(p.stdout)
|
||||
make_async(p.stderr)
|
||||
|
||||
stdout = []
|
||||
while True:
|
||||
# Wait for new output
|
||||
select.select([p.stdout, p.stderr], [], [])
|
||||
select.select([p.stdout, p.stderr], [], [], 0.1)
|
||||
|
||||
stdout.append(read_async(p.stdout))
|
||||
stderr = read_async(p.stderr)
|
||||
|
@ -184,6 +177,7 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
|
|||
logger.warning(msg)
|
||||
|
||||
if p.poll() is not None:
|
||||
time.sleep(0.1)
|
||||
stdout.append(read_async(p.stdout))
|
||||
break
|
||||
|
||||
|
@ -203,10 +197,6 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
|
|||
log_entry.repo_url = self.params.get('repo_url', None)
|
||||
log_entry.save()
|
||||
|
||||
# Ensure async reading of mock stdout/stderr is finished.
|
||||
if hasattr(sys, '_called_from_test'):
|
||||
time.sleep(1)
|
||||
|
||||
self.process_result(result)
|
||||
self.finished_event(result)
|
||||
mutex.unlock()
|
||||
|
|
|
@ -256,6 +256,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin):
|
|||
window = ExtractDialog(result['data'], archive)
|
||||
self._toggle_all_buttons(True)
|
||||
window.setParent(self, QtCore.Qt.Sheet)
|
||||
self._window = window # for testing
|
||||
window.show()
|
||||
|
||||
if window.exec_():
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import sys
|
||||
import os
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
|
||||
from PyQt5 import uic
|
||||
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
|
||||
from PyQt5.QtWidgets import QApplication, QHeaderView
|
||||
|
@ -10,29 +13,30 @@ uifile = get_asset('UI/extractdialog.ui')
|
|||
ExtractDialogUI, ExtractDialogBase = uic.loadUiType(uifile)
|
||||
ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
|
||||
|
||||
full_list = []
|
||||
folder_list = nested_dict()
|
||||
selected = set()
|
||||
files_with_attributes = []
|
||||
nested_file_list = nested_dict()
|
||||
selected_files_folders = set()
|
||||
|
||||
|
||||
class ExtractDialog(ExtractDialogBase, ExtractDialogUI):
|
||||
def __init__(self, fs_data, archive):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
global full_list, folder_list, selected
|
||||
global files_with_attributes, nested_file_list, selected_files_folders
|
||||
|
||||
def parse_line(line):
|
||||
size, modified, full_path = line.split('\t')
|
||||
size = int(size)
|
||||
dir, name = os.path.split(full_path)
|
||||
if size == 0:
|
||||
d = get_dict_from_list(folder_list, dir.split('/'))
|
||||
if name not in d:
|
||||
d[name] = {}
|
||||
|
||||
# add to nested dict of folders to find nested dirs.
|
||||
d = get_dict_from_list(nested_file_list, dir.split('/'))
|
||||
if name not in d:
|
||||
d[name] = {}
|
||||
|
||||
return size, modified, name, dir
|
||||
|
||||
full_list = [parse_line(l) for l in fs_data.split('\n')[:-1]]
|
||||
files_with_attributes = [parse_line(l) for l in fs_data.split('\n')[:-1]]
|
||||
|
||||
model = TreeModel()
|
||||
|
||||
|
@ -49,7 +53,7 @@ class ExtractDialog(ExtractDialogBase, ExtractDialogUI):
|
|||
self.archiveNameLabel.setText(f'{archive.name}, {archive.time}')
|
||||
self.cancelButton.clicked.connect(self.close)
|
||||
self.extractButton.clicked.connect(self.accept)
|
||||
self.selected = selected
|
||||
self.selected = selected_files_folders
|
||||
|
||||
|
||||
class FileItem:
|
||||
|
@ -81,11 +85,11 @@ class FileItem:
|
|||
def setCheckedState(self, value):
|
||||
if value == 2:
|
||||
self.checkedState = True
|
||||
selected.add(
|
||||
selected_files_folders.add(
|
||||
os.path.join(self.parentItem.path, self.parentItem.data(0), self.itemData[0]))
|
||||
else:
|
||||
self.checkedState = False
|
||||
selected.remove(
|
||||
selected_files_folders.remove(
|
||||
os.path.join(self.parentItem.path, self.parentItem.data(0), self.itemData[0]))
|
||||
|
||||
def getCheckedState(self):
|
||||
|
@ -107,12 +111,15 @@ class FolderItem(FileItem):
|
|||
self._filtered_children = []
|
||||
search_path = os.path.join(self.path, name)
|
||||
if parent is None: # Find path for root folder
|
||||
for root_folder in folder_list.keys():
|
||||
for root_folder in nested_file_list.keys():
|
||||
self._filtered_children.append((0, '', root_folder, '', ))
|
||||
else:
|
||||
self._filtered_children = [f for f in full_list if search_path == f[3]]
|
||||
if self.childCount() == 0: # If there are no immediate children, we try the next-deepest folder.
|
||||
for immediate_child in get_dict_from_list(folder_list, search_path.split('/')).keys():
|
||||
# This adds direct children.
|
||||
self._filtered_children = [f for f in files_with_attributes if search_path == f[3]]
|
||||
|
||||
# Add nested folders.
|
||||
for immediate_child in get_dict_from_list(nested_file_list, search_path.split('/')).keys():
|
||||
if not [True for child in self._filtered_children if child[2] == immediate_child]:
|
||||
self._filtered_children.append((0, '', immediate_child, search_path))
|
||||
|
||||
self.is_loaded = False
|
||||
|
@ -254,8 +261,16 @@ class TreeModel(QAbstractItemModel):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
For local testing:
|
||||
|
||||
borg list --progress --info --log-json --format="{size:8d}{TAB}{mtime}{TAB}{path}{NL}"
|
||||
"""
|
||||
FakeArchive = namedtuple('Archive', ['name', 'time'])
|
||||
app = QApplication(sys.argv)
|
||||
test_list = open('/Users/manu/Downloads/archive_list.txt').read()
|
||||
view = ExtractDialog(test_list.split('\n'))
|
||||
test_list = open('/Users/manu/Downloads/nyx2-list.txt').read()
|
||||
|
||||
archive = FakeArchive('test-archive', datetime.datetime.now())
|
||||
view = ExtractDialog(test_list, archive)
|
||||
view.show()
|
||||
sys.exit(app.exec_())
|
||||
|
|
|
@ -83,7 +83,7 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
window = AddProfileWindow()
|
||||
window.setParent(self, QtCore.Qt.Sheet)
|
||||
window.show()
|
||||
if window.exec_():
|
||||
if window.exec_() and window.edited_profile:
|
||||
self.profileSelector.addItem(window.edited_profile.name, window.edited_profile.id)
|
||||
self.profileSelector.setCurrentIndex(self.profileSelector.count() - 1)
|
||||
else:
|
||||
|
|
|
@ -82,6 +82,8 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
ssh_add_window.show()
|
||||
if ssh_add_window.exec_():
|
||||
self.init_ssh()
|
||||
else:
|
||||
self.sshComboBox.setCurrentIndex(0)
|
||||
else:
|
||||
profile = self.profile()
|
||||
profile.ssh_key = self.sshComboBox.itemData(index)
|
||||
|
@ -129,6 +131,9 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
window.show()
|
||||
if window.exec_():
|
||||
self.process_new_repo(window.result)
|
||||
else:
|
||||
self.repoSelector.setCurrentIndex(0)
|
||||
|
||||
else:
|
||||
profile = self.profile()
|
||||
profile.repo = self.repoSelector.currentData()
|
||||
|
|
|
@ -45,6 +45,7 @@ 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):
|
||||
|
|
6
tests/borg_json_output/check_stderr.json
Normal file
6
tests/borg_json_output/check_stderr.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{"name": "borg.repository", "message": "Remote: Starting repository check", "type": "log_message", "levelname": "INFO", "time": 1543501427.3991857}
|
||||
{"name": "borg.repository", "message": "Remote: Starting repository index check", "type": "log_message", "levelname": "INFO", "time": 1543501428.2007568}
|
||||
{"name": "borg.repository", "message": "Remote: Completed repository check, no problems found.", "type": "log_message", "levelname": "INFO", "time": 1543501428.201012}
|
||||
{"type": "log_message", "time": 1543501427.141906, "message": "Starting archive consistency check...", "levelname": "INFO", "name": "borg.archive"}
|
||||
{"type": "log_message", "time": 1543501429.413376, "message": "Analyzing archive nyx2.local-1-2018-11-26T15:33:25 (1/1)", "levelname": "INFO", "name": "borg.archive"}
|
||||
{"type": "log_message", "time": 1543501430.631774, "message": "Archive consistency check complete, no problems found.", "levelname": "INFO", "name": "borg.archive"}
|
6
tests/borg_json_output/check_stdout.json
Normal file
6
tests/borg_json_output/check_stdout.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{"name": "borg.repository", "message": "Remote: Starting repository check", "type": "log_message", "levelname": "INFO", "time": 1543501427.3991857}
|
||||
{"name": "borg.repository", "message": "Remote: Starting repository index check", "type": "log_message", "levelname": "INFO", "time": 1543501428.2007568}
|
||||
{"name": "borg.repository", "message": "Remote: Completed repository check, no problems found.", "type": "log_message", "levelname": "INFO", "time": 1543501428.201012}
|
||||
{"type": "log_message", "time": 1543501427.141906, "message": "Starting archive consistency check...", "levelname": "INFO", "name": "borg.archive"}
|
||||
{"type": "log_message", "time": 1543501429.413376, "message": "Analyzing archive nyx2.local-1-2018-11-26T15:33:25 (1/1)", "levelname": "INFO", "name": "borg.archive"}
|
||||
{"type": "log_message", "time": 1543501430.631774, "message": "Archive consistency check complete, no problems found.", "levelname": "INFO", "name": "borg.archive"}
|
20
tests/borg_json_output/list_archive_stderr.json
Normal file
20
tests/borg_json_output/list_archive_stderr.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{"type": "log_message", "time": 1541485706.185808, "message": "using builtin fallback logging configuration", "levelname": "DEBUG", "name": "borg.logger"}
|
||||
{"type": "log_message", "time": 1541485706.329196, "message": "35 self tests completed in 0.14 seconds", "levelname": "DEBUG", "name": "borg.archiver"}
|
||||
{"type": "log_message", "time": 1541485706.32982, "message": "SSH command line: ['ssh', 'w66xh7lj@w66xh7lj.repo.borgbase.com', 'borg', 'serve', '--umask=077', '--debug']", "levelname": "DEBUG", "name": "borg.remote"}
|
||||
{"type": "log_message", "time": 1541485712.0352168, "message": "Remote: using builtin fallback logging configuration", "levelname": "DEBUG", "name": "borg.logger"}
|
||||
{"type": "log_message", "time": 1541485712.10305, "message": "Remote: 35 self tests completed in 0.16 seconds", "levelname": "DEBUG", "name": "borg.archiver"}
|
||||
{"type": "log_message", "time": 1541485711.9786682, "levelname": "DEBUG", "message": "Remote: using builtin fallback logging configuration", "name": "borg.logger"}
|
||||
{"type": "log_message", "time": 1541485711.9788692, "levelname": "DEBUG", "message": "Remote: Initialized logging system for JSON-based protocol", "name": "borg.remote"}
|
||||
{"type": "log_message", "time": 1541485712.2395813, "levelname": "DEBUG", "message": "Remote: Resolving repository path b'repo'", "name": "root"}
|
||||
{"type": "log_message", "time": 1541485712.240386, "levelname": "DEBUG", "message": "Remote: Resolved repository path to '/srv/repos/w66xh7lj/repo'", "name": "root"}
|
||||
{"type": "log_message", "time": 1541485712.7960937, "levelname": "DEBUG", "message": "Remote: Verified integrity of /srv/repos/w66xh7lj/repo/index.153", "name": "borg.crypto.file_integrity"}
|
||||
{"type": "log_message", "time": 1541485713.763066, "message": "TAM-verified manifest", "levelname": "DEBUG", "name": "borg.crypto.key"}
|
||||
{"type": "log_message", "time": 1541485713.779689, "message": "security: read previous location 'ssh://w66xh7lj@w66xh7lj.repo.borgbase.com/./repo'", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.780284, "message": "security: read manifest timestamp '2018-11-06T06:24:14.199720'", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.780406, "message": "security: determined newest manifest timestamp as 2018-11-06T06:24:14.199720", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.7819798, "message": "security: repository checks ok, allowing access", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.7866921, "message": "Verified integrity of /Users/manu/.cache/borg/daf2e2b94a1b57f0effc96939813ef58d0af04414f92f87c3e092a99adaa90eb/chunks", "levelname": "DEBUG", "name": "borg.crypto.file_integrity"}
|
||||
{"type": "log_message", "time": 1541485713.7872949, "message": "security: read previous location 'ssh://w66xh7lj@w66xh7lj.repo.borgbase.com/./repo'", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.787873, "message": "security: read manifest timestamp '2018-11-06T06:24:14.199720'", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.788007, "message": "security: determined newest manifest timestamp as 2018-11-06T06:24:14.199720", "levelname": "DEBUG", "name": "borg.cache"}
|
||||
{"type": "log_message", "time": 1541485713.788181, "message": "security: repository checks ok, allowing access", "levelname": "DEBUG", "name": "borg.cache"}
|
69
tests/borg_json_output/list_archive_stdout.json
Normal file
69
tests/borg_json_output/list_archive_stdout.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
0 Thu, 2018-11-29 10:35:09 Users/manu/Desktop
|
||||
0 Wed, 2013-09-04 00:54:20 Users/manu/Desktop/.ipynb_checkpoints
|
||||
247348 Wed, 2013-09-04 01:00:31 Users/Untitled7-checkpoint.ipynb
|
||||
888 Wed, 2018-03-21 23:18:32 Users/manu/Desktop/Receipts
|
||||
800 Wed, 2018-03-21 23:18:58 Users/manu/Desktop/Documents
|
||||
840 Tue, 2018-03-27 00:42:58 Users/manu/Desktop/travel
|
||||
199 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.au/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.my
|
||||
199 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com.my/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com
|
||||
196 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.com/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.de
|
||||
195 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tripadvisor.de/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tunescoop.com
|
||||
194 Fri, 2017-02-24 12:13:41 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.tunescoop.com/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:42 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.udemy.com
|
||||
190 Fri, 2017-02-24 12:13:42 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.udemy.com/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ulozto.net
|
||||
191 Fri, 2017-02-24 12:13:39 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ulozto.net/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:44 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ultimedia.com
|
||||
194 Fri, 2017-02-24 12:13:44 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.ultimedia.com/settings.sol
|
||||
0 Fri, 2017-02-24 12:13:38 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.videodetective.net
|
||||
199 Fri, 2017-02-24 12:13:38 Users/manu/Library/Application Support/Google/Chrome/Default/Pepper Data/Shockwave Flash/WritableRoot/#SharedObjects/K4JZRG4W/macromedia.com/support/flashplayer/sys/#www.videodetective.net/settings.sol
|
||||
188016 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/NuFS.framework/Versions/A/NuFS
|
||||
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext
|
||||
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents
|
||||
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/MacOS
|
||||
229692 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/MacOS/transmitdiskfs
|
||||
1908 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/transmitdiskfs.kext/Contents/Info.plist
|
||||
339916 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/libfuse.dylib
|
||||
47376 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/load_nufs
|
||||
74416 Fri, 2014-09-19 05:52:28 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/com.panic.TransmitDisk.transmitdiskfs.components/mount_nufs
|
||||
65853 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Resources/License.pdf
|
||||
2682 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/Info.plist
|
||||
551 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/transmitdiskfs.fs/Contents/version.plist
|
||||
129893 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/disk-transmit.icns
|
||||
1178 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Resources/sparkle_dsa_pub.pem
|
||||
0 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/_CodeSignature
|
||||
8378 Fri, 2014-09-19 05:52:30 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/_CodeSignature/CodeResources
|
||||
1768 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/Info.plist
|
||||
8 Fri, 2014-09-19 05:51:56 Users/manu/Library/Application Support/Transmit/Transmit Disk.app/Contents/PkgInfo
|
||||
239529 Thu, 2018-11-29 08:51:51 Users/manu/Library/Application Support/Transmit/Connections.transmitstore
|
||||
0 Tue, 2018-11-13 09:02:33 Users/manu/Library/Application Support/coconutBattery
|
||||
30939 Mon, 2017-09-11 22:52:42 Users/manu/Library/Application Support/coconutBattery/Mac.ccbarchive.backup_3.6.3
|
||||
31097 Sat, 2017-04-08 18:45:35 Users/manu/Library/Application Support/coconutBattery/coconutBatteryData.ccbarchive
|
||||
98162 Tue, 2018-11-13 09:02:33 Users/manu/Library/Application Support/coconutBattery/Mac.ccbarchive
|
||||
84803 Tue, 2013-12-10 13:41:58 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2013-11-02.PDF
|
||||
84228 Tue, 2013-12-10 13:42:02 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2013-12-02.PDF
|
||||
85337 Tue, 2014-01-07 21:28:09 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-01-02.PDF
|
||||
84228 Mon, 2014-04-07 22:48:28 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-02-02.PDF
|
||||
85095 Mon, 2014-04-07 22:52:59 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-03-02.PDF
|
||||
83329 Mon, 2014-04-07 22:53:03 Users/manu/Documents/financial/_archive/Diners Club/kontoauszug-2014-04-02.PDF
|
||||
0 Tue, 2016-03-15 19:29:23 Users/manu/Documents/financial/_archive/Direktanlage
|
||||
117360 Tue, 2013-08-27 14:53:25 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300001.PDF
|
||||
156800 Wed, 2013-10-16 20:21:38 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300002.PDF
|
||||
80720 Sat, 2014-01-04 21:05:34 Users/manu/Documents/financial/_archive/Direktanlage/19250_65300182206_EUR_201300003.PDF
|
||||
1292665 Mon, 2012-07-23 05:26:43 Users/manu/Documents/cooking/IMG_20120505_160225.jpg
|
||||
1330876 Mon, 2012-07-23 05:26:40 Users/manu/Documents/cooking/IMG_20120505_160425.jpg
|
||||
1219294 Mon, 2012-07-23 05:26:43 Users/manu/Documents/cooking/IMG_20120505_160842.jpg
|
||||
1205672 Mon, 2012-07-23 05:26:44 Users/manu/Documents/cooking/IMG_20120505_161225.jpg
|
||||
1254561 Mon, 2012-07-23 05:26:42 Users/manu/Documents/cooking/IMG_20120505_161247.jpg
|
||||
1141842 Mon, 2012-07-23 05:26:44 Users/manu/Documents/cooking/IMG_20120505_161525.jpg
|
||||
1143566 Mon, 2012-07-23 05:26:37 Users/manu/Documents/cooking/IMG_20120505_162547.jpg
|
||||
1015435 Sat, 2012-02-25 21:55:12 Users/manu/Documents/cooking/Jamie Oliver Tuna in Tomatoe Sauce.pdf
|
||||
33762 Fri, 2012-11-02 23:19:19 Users/manu/Documents/cooking/Mediterranean Beef Stew with Rosemary.pdf
|
||||
1225647 Mon, 2012-07-23 05:26:50 Users/manu/Documents/cooking/Schweinekotelett und Ratatouille.jpg
|
||||
1038737 Wed, 2012-12-12 21:38:43 Users/manu/Documents/cooking/Shrimp, Avocado and Red Pepper Salad.pdf
|
||||
65393 Tue, 2012-11-13 22:04:04 Users/manu/Documents/cooking/Tiramisu Semifreddo.pdf
|
||||
1266835 Mon, 2012-07-23 05:26:50 Users/manu/Documents/cooking/Tomaten-Paprika Suppe.jpg
|
2
tests/borg_json_output/prune_stderr.json
Normal file
2
tests/borg_json_output/prune_stderr.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{"levelname": "INFO", "message": "Remote: Storage quota: 10.51 MB out of 1.00 GB used.", "time": 1543489279.0468729, "type": "log_message", "name": "borg.repository"}
|
||||
{"levelname": "INFO", "message": "Remote: Storage quota: 10.49 MB out of 1.00 GB used.", "time": 1543489282.1030364, "type": "log_message", "name": "borg.repository"}
|
0
tests/borg_json_output/prune_stdout.json
Normal file
0
tests/borg_json_output/prune_stdout.json
Normal file
|
@ -1,37 +1,54 @@
|
|||
import pytest
|
||||
import peewee
|
||||
import sys
|
||||
from datetime import datetime as dt
|
||||
|
||||
import vorta
|
||||
from vorta.application import VortaApp
|
||||
from vorta.models import RepoModel, SourceDirModel
|
||||
from vorta.models import RepoModel, SourceDirModel, ArchiveModel, BackupProfileModel
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
import sys
|
||||
sys._called_from_test = True
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def app(tmpdir, qtbot):
|
||||
tmp_db = tmpdir.join('settings.sqlite')
|
||||
mock_db = peewee.SqliteDatabase(str(tmp_db))
|
||||
vorta.models.init_db(mock_db)
|
||||
|
||||
new_repo = RepoModel(url='i0fi93@i593.repo.borgbase.com:repo')
|
||||
new_repo.save()
|
||||
|
||||
profile = BackupProfileModel.get(id=1)
|
||||
profile.repo = new_repo.id
|
||||
profile.save()
|
||||
|
||||
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.save()
|
||||
|
||||
app = VortaApp([])
|
||||
qtbot.addWidget(app.main_window)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def app_with_repo(app):
|
||||
profile = app.main_window.current_profile
|
||||
new_repo = RepoModel(url='i0fi93@i593.repo.borgbase.com:repo')
|
||||
new_repo.save()
|
||||
profile.repo = new_repo
|
||||
profile.save()
|
||||
@pytest.fixture
|
||||
def choose_folder_dialog(*args):
|
||||
class MockFileDialog:
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
source_dir = SourceDirModel(dir='/tmp', repo=new_repo)
|
||||
source_dir.save()
|
||||
return app
|
||||
def open(self, func):
|
||||
func()
|
||||
|
||||
def selectedFiles(self):
|
||||
return ['/tmp']
|
||||
|
||||
return MockFileDialog
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
import psutil
|
||||
from collections import namedtuple
|
||||
from PyQt5 import QtCore
|
||||
from vorta.models import BackupProfileModel, ArchiveModel
|
||||
import vorta.borg
|
||||
import vorta.views.archive_tab
|
||||
import vorta.utils
|
||||
|
||||
|
||||
class MockFileDialog:
|
||||
def open(self, func):
|
||||
func()
|
||||
|
||||
def selectedFiles(self):
|
||||
return ['/tmp']
|
||||
|
||||
|
||||
def test_prune_intervals(app, qtbot):
|
||||
|
@ -15,8 +28,8 @@ def test_prune_intervals(app, qtbot):
|
|||
assert getattr(profile, f'prune_{i}') == 9
|
||||
|
||||
|
||||
def test_repo_list(app_with_repo, qtbot, mocker, borg_json_output):
|
||||
main = app_with_repo.main_window
|
||||
def test_repo_list(app, qtbot, mocker, borg_json_output):
|
||||
main = app.main_window
|
||||
tab = main.archiveTab
|
||||
main.tabWidget.setCurrentIndex(3)
|
||||
tab.list_action()
|
||||
|
@ -30,3 +43,94 @@ def test_repo_list(app_with_repo, qtbot, mocker, borg_json_output):
|
|||
assert ArchiveModel.select().count() == 6
|
||||
assert main.createProgressText.text() == 'Refreshing snapshots done.'
|
||||
assert tab.checkButton.isEnabled()
|
||||
|
||||
|
||||
def test_repo_prune(app, qtbot, mocker, borg_json_output):
|
||||
main = app.main_window
|
||||
tab = main.archiveTab
|
||||
main.tabWidget.setCurrentIndex(3)
|
||||
tab.populate_from_profile()
|
||||
stdout, stderr = borg_json_output('prune')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
||||
qtbot.mouseClick(tab.pruneButton, QtCore.Qt.LeftButton)
|
||||
|
||||
qtbot.waitUntil(lambda: main.createProgressText.text().startswith('Refreshing snapshots'))
|
||||
|
||||
|
||||
def test_check(app, mocker, borg_json_output, qtbot):
|
||||
main = app.main_window
|
||||
tab = main.archiveTab
|
||||
main.tabWidget.setCurrentIndex(3)
|
||||
tab.populate_from_profile()
|
||||
|
||||
stdout, stderr = borg_json_output('check')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
||||
qtbot.mouseClick(tab.checkButton, QtCore.Qt.LeftButton)
|
||||
success_text = 'INFO: Archive consistency check complete'
|
||||
qtbot.waitUntil(lambda: main.createProgressText.text().startswith(success_text))
|
||||
|
||||
|
||||
def test_archive_mount(app, qtbot, mocker, borg_json_output, monkeypatch, choose_folder_dialog):
|
||||
def psutil_disk_partitions(**kwargs):
|
||||
DiskPartitions = namedtuple('DiskPartitions', ['device', 'mountpoint'])
|
||||
return [DiskPartitions('borgfs', '/tmp')]
|
||||
|
||||
monkeypatch.setattr(
|
||||
psutil, "disk_partitions", psutil_disk_partitions
|
||||
)
|
||||
|
||||
main = app.main_window
|
||||
tab = main.archiveTab
|
||||
main.tabWidget.setCurrentIndex(3)
|
||||
tab.populate_from_profile()
|
||||
tab.archiveTable.selectRow(0)
|
||||
|
||||
stdout, stderr = borg_json_output('prune')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
||||
monkeypatch.setattr(
|
||||
vorta.views.archive_tab, "choose_folder_dialog", choose_folder_dialog
|
||||
)
|
||||
|
||||
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
|
||||
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Mounted'), timeout=1000)
|
||||
|
||||
qtbot.mouseClick(tab.mountButton, QtCore.Qt.LeftButton)
|
||||
# qtbot.waitUntil(lambda: tab.mountErrors.text() == 'No active Borg mounts found.')
|
||||
|
||||
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Un-mounted successfully.'), timeout=5000)
|
||||
|
||||
|
||||
def test_archive_extract(app, qtbot, mocker, borg_json_output, monkeypatch):
|
||||
main = app.main_window
|
||||
tab = main.archiveTab
|
||||
main.tabWidget.setCurrentIndex(3)
|
||||
|
||||
tab.populate_from_profile()
|
||||
qtbot.waitUntil(lambda: tab.archiveTable.rowCount() == 1)
|
||||
|
||||
qtbot.mouseClick(tab.extractButton, QtCore.Qt.LeftButton)
|
||||
qtbot.waitUntil(lambda: tab.mountErrors.text().startswith('Select an archive'))
|
||||
|
||||
monkeypatch.setattr(
|
||||
vorta.views.extract_dialog.ExtractDialog, "exec_", lambda *args: True
|
||||
)
|
||||
|
||||
tab.archiveTable.selectRow(0)
|
||||
stdout, stderr = borg_json_output('list_archive')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
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'))
|
||||
|
||||
assert tab._window.treeView.model().rootItem.childItems[0].data(0) == 'Users'
|
||||
tab._window.treeView.model().rootItem.childItems[0].load_children()
|
||||
|
||||
assert tab._window.archiveNameLabel.text().startswith('test-archive, 2000')
|
||||
tab._window.accept()
|
||||
|
|
18
tests/test_borg.py
Normal file
18
tests/test_borg.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import vorta.borg
|
||||
import vorta.models
|
||||
from vorta.borg.prune import BorgPruneThread
|
||||
|
||||
|
||||
def test_borg_prune(app, qtbot, mocker, borg_json_output):
|
||||
stdout, stderr = borg_json_output('prune')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
||||
params = BorgPruneThread.prepare(vorta.models.BackupProfileModel.select().first())
|
||||
thread = BorgPruneThread(params['cmd'], params, app)
|
||||
|
||||
with qtbot.waitSignal(thread.result, timeout=10000) as blocker:
|
||||
blocker.connect(thread.updated)
|
||||
thread.run()
|
||||
|
||||
assert blocker.args[0]['returncode'] == 0
|
|
@ -1,17 +1,14 @@
|
|||
import os
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
import vorta.borg.borg_thread
|
||||
import vorta.models
|
||||
from vorta.views.repo_add_dialog import AddRepoWindow
|
||||
from vorta.views.ssh_dialog import SSHAddWindow
|
||||
from vorta.models import EventLogModel, RepoModel, ArchiveModel
|
||||
|
||||
|
||||
def test_create_fail(app, qtbot):
|
||||
main = app.main_window
|
||||
qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton)
|
||||
assert main.createProgressText.text() == 'Add a backup repository first.'
|
||||
|
||||
|
||||
def test_repo_add(app, qtbot, mocker, borg_json_output):
|
||||
# Add new repo window
|
||||
main = app.main_window
|
||||
|
@ -38,11 +35,50 @@ 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=1).url == 'aaabbb.com:repo'
|
||||
assert RepoModel.get(id=2).url == 'aaabbb.com:repo'
|
||||
|
||||
|
||||
def test_create(app_with_repo, borg_json_output, mocker, qtbot):
|
||||
main = app_with_repo.main_window
|
||||
def test_repo_unlink(app, qtbot, monkeypatch):
|
||||
monkeypatch.setattr(QMessageBox, "exec_", lambda *args: QMessageBox.Yes)
|
||||
main = app.main_window
|
||||
tab = main.repoTab
|
||||
main.tabWidget.setCurrentIndex(0)
|
||||
qtbot.mouseClick(tab.repoRemoveToolbutton, QtCore.Qt.LeftButton)
|
||||
|
||||
qtbot.waitUntil(lambda: tab.repoSelector.count() == 3, timeout=5000)
|
||||
assert tab.repoSelector.count() == 3
|
||||
assert RepoModel.select().count() == 0
|
||||
|
||||
qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton)
|
||||
assert main.createProgressText.text() == 'Add a backup repository first.'
|
||||
|
||||
|
||||
def test_ssh_dialog(qtbot, tmpdir):
|
||||
ssh_dialog = SSHAddWindow()
|
||||
ssh_dir = tmpdir
|
||||
key_tmpfile = ssh_dir.join("id_rsa-test")
|
||||
pub_tmpfile = ssh_dir.join("id_rsa-test.pub")
|
||||
key_tmpfile_full = os.path.join(key_tmpfile.dirname, key_tmpfile.basename)
|
||||
ssh_dialog.outputFileTextBox.setText(key_tmpfile_full)
|
||||
qtbot.mouseClick(ssh_dialog.generateButton, QtCore.Qt.LeftButton)
|
||||
|
||||
qtbot.waitUntil(lambda: key_tmpfile.check(file=1))
|
||||
|
||||
key_tmpfile_content = key_tmpfile.read()
|
||||
pub_tmpfile_content = pub_tmpfile.read()
|
||||
assert key_tmpfile_content.startswith('-----BEGIN OPENSSH PRIVATE KEY-----')
|
||||
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'))
|
||||
|
||||
|
||||
def test_create(app, borg_json_output, mocker, qtbot):
|
||||
main = app.main_window
|
||||
stdout, stderr = borg_json_output('create')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
@ -51,8 +87,8 @@ def test_create(app_with_repo, borg_json_output, mocker, qtbot):
|
|||
qtbot.waitUntil(lambda: main.createProgressText.text().startswith('Backup finished.'), timeout=3000)
|
||||
qtbot.waitUntil(lambda: main.createStartBtn.isEnabled(), timeout=3000)
|
||||
assert EventLogModel.select().count() == 1
|
||||
assert ArchiveModel.select().count() == 1
|
||||
assert ArchiveModel.select().count() == 2
|
||||
assert RepoModel.get(id=1).unique_size == 15520474
|
||||
assert main.createStartBtn.isEnabled()
|
||||
assert main.archiveTab.archiveTable.rowCount() == 1
|
||||
assert main.archiveTab.archiveTable.rowCount() == 2
|
||||
assert main.scheduleTab.logTableWidget.rowCount() == 1
|
||||
|
|
12
tests/test_scheduler.py
Normal file
12
tests/test_scheduler.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
import vorta.borg
|
||||
import vorta.models
|
||||
|
||||
|
||||
def test_scheduler_create_backup(app, qtbot, mocker, borg_json_output):
|
||||
stdout, stderr = borg_json_output('create')
|
||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||
mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)
|
||||
|
||||
app.scheduler.create_backup(1)
|
||||
|
||||
qtbot.waitUntil(lambda: vorta.models.EventLogModel.select().count() == 2, timeout=5000)
|
19
tests/test_source.py
Normal file
19
tests/test_source.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import logging
|
||||
from PyQt5 import QtCore
|
||||
import vorta.models
|
||||
|
||||
|
||||
def test_add_folder(app, qtbot, tmpdir):
|
||||
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():
|
||||
logging.error(src.dir, src.profile)
|
Loading…
Add table
Reference in a new issue