Replace peewee_migrate with native solution. Fix window activation.

This commit is contained in:
Manu 2018-11-02 00:53:41 +08:00
parent 8bf2c54c38
commit 3a5eafd061
18 changed files with 246 additions and 222 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ __pycache__
Makefile
.eggs
vorta.egg-info
.coverage

View File

@ -19,9 +19,9 @@ Missing features: (PRs welcome)
- [ ] Repo pruning
- [ ] Repo checking
- [ ] Full test coverage
- [ ] Use static type checks via mypy
- [ ] Packaging for Linux
- [ ] Full test coverage (currently: 65%)
- [ ] Use static type checks via mypy?
- [ ] Packaging for Linux? How?
## Installation and Download
### macOS
@ -55,8 +55,6 @@ $ brew cask install qt-creator
$ brew install qt
```
[Peewee Migrate](https://github.com/klen/peewee_migrate) is used to manage database migrations. Add them to the `vorta.migrations` package.
To build a binary package:
```
$ pyinstaller --clean --noconfirm vorta.spec
@ -68,6 +66,12 @@ Tests are in the folder `/tests`. Testing happens at the level of UI components.
$ pytest
```
To update and view coverage information
```
$ coverage run -m pytest
$ coverage report
```
## Privacy Policy
- No personal data is ever stored or transmitted by this application.
- During beta, crash reports are sent to [Sentry](https://sentry.io) to quickly find bugs.

View File

@ -41,7 +41,6 @@ install_requires =
python-dateutil
keyring
borgbackup
peewee_migrate
[options.extras_require]
tests =
@ -51,6 +50,7 @@ tests =
pytest-faulthandler
mypy
mypy-extensions
coverage
[options.entry_points]
gui_scripts =
@ -64,4 +64,7 @@ qt_default_raising = true
filterwarnings =
ignore::DeprecationWarning
[coverage:run]
source = src
[tox:tox]

View File

@ -3,7 +3,6 @@ import os
import peewee
import vorta.models
import vorta.migrations
from vorta.application import VortaApp
from vorta.config import SETTINGS_DIR
@ -16,10 +15,5 @@ if getattr(sys, 'frozen', False):
sqlite_db = peewee.SqliteDatabase(os.path.join(SETTINGS_DIR, 'settings.db'))
vorta.models.init_db(sqlite_db)
# Run migrations
from peewee_migrate.cli import get_router
router = get_router(os.path.dirname(vorta.migrations.__file__), sqlite_db, True)
router.run()
app = VortaApp(sys.argv)
sys.exit(app.exec_())

View File

@ -1,5 +1,5 @@
import sys
import os
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication
@ -47,8 +47,8 @@ class VortaApp(QApplication, BackupProfileMixin):
return msg
def on_open_main_window(self):
self.main_window = MainWindow(self)
self.main_window.show()
self.main_window.raise_()
def create_backup_result(self, result):
self.backup_done.emit()

View File

@ -35,7 +35,7 @@
<x>0</x>
<y>0</y>
<width>801</width>
<height>561</height>
<height>571</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -154,6 +154,11 @@
</item>
<item row="1" column="1">
<widget class="QLabel" name="createProgressText">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>

View File

@ -81,7 +81,7 @@
<item row="3" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SSH account with Borg installed server-side. Also try &lt;a href=&quot;https://www.borgbase.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BorgBase.&lt;/span&gt;&lt;/a&gt; 100GB free during Beta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;SSH account with Borg installed server-side. Also try &lt;a href=&quot;https://www.borgbase.com?utm_source=vorta&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BorgBase&lt;/span&gt;&lt;/a&gt;. 100GB free during Beta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>

View File

@ -47,6 +47,16 @@
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Duration</string>
</property>
</column>
<column>
<property name="text">
<string>Date</string>

View File

@ -1,6 +1,5 @@
import appdirs
import os
import shutil
APP_NAME = 'Vorta'
APP_AUTHOR = 'BorgBase'
@ -8,6 +7,3 @@ SETTINGS_DIR = appdirs.user_data_dir(APP_NAME, APP_AUTHOR)
if not os.path.exists(SETTINGS_DIR):
os.makedirs(SETTINGS_DIR)
def remove_config():
shutil.rmtree(SETTINGS_DIR)

View File

@ -1,38 +0,0 @@
"""Peewee migrations -- 001_add-snapshot-size.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
import peewee as pw
from vorta.models import *
SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs):
migrator.add_fields(SnapshotModel, duration=pw.FloatField(null=True))
migrator.add_fields(SnapshotModel, size=pw.IntegerField(null=True))
def rollback(migrator, database, fake=False, **kwargs):
"""Write your rollback migrations here."""

View File

@ -1,11 +1,20 @@
import peewee
"""
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 json
from datetime import datetime
from playhouse.migrate import SqliteMigrator, migrate
db = peewee.Proxy()
SCHEMA_VERSION = 4
db = pw.Proxy()
class JSONField(peewee.TextField):
class JSONField(pw.TextField):
"""
Class to "fake" a JSON field with a text field. Not efficient but works nicely
@ -21,65 +30,65 @@ class JSONField(peewee.TextField):
class RepoModel(peewee.Model):
class RepoModel(pw.Model):
"""A single remote repo with unique URL."""
url = peewee.CharField(unique=True)
added_at = peewee.DateTimeField(default=datetime.utcnow)
encryption = peewee.CharField(null=True)
unique_size = peewee.IntegerField(null=True)
unique_csize = peewee.IntegerField(null=True)
total_size = peewee.IntegerField(null=True)
total_unique_chunks = peewee.IntegerField(null=True)
url = pw.CharField(unique=True)
added_at = pw.DateTimeField(default=datetime.utcnow)
encryption = pw.CharField(null=True)
unique_size = pw.IntegerField(null=True)
unique_csize = pw.IntegerField(null=True)
total_size = pw.IntegerField(null=True)
total_unique_chunks = pw.IntegerField(null=True)
class Meta:
database = db
class BackupProfileModel(peewee.Model):
class BackupProfileModel(pw.Model):
"""Allows the user to switch between different configurations."""
name = peewee.CharField()
added_at = peewee.DateTimeField(default=datetime.now)
repo = peewee.ForeignKeyField(RepoModel, default=None, null=True)
ssh_key = peewee.CharField(default=None, null=True)
compression = peewee.CharField(default='lz4')
exclude_patterns = peewee.TextField(null=True)
exclude_if_present = peewee.TextField(null=True)
schedule_mode = peewee.CharField(default='off')
schedule_interval_hours = peewee.IntegerField(default=3)
schedule_interval_minutes = peewee.IntegerField(default=42)
schedule_fixed_hour = peewee.IntegerField(default=3)
schedule_fixed_minute = peewee.IntegerField(default=42)
validation_on = peewee.BooleanField(default=True)
validation_weeks = peewee.IntegerField(default=3)
prune_on = peewee.BooleanField(default=False)
prune_hour = peewee.IntegerField(default=2)
prune_day = peewee.IntegerField(default=7)
prune_week = peewee.IntegerField(default=4)
prune_month = peewee.IntegerField(default=6)
prune_year = peewee.IntegerField(default=2)
name = pw.CharField()
added_at = pw.DateTimeField(default=datetime.now)
repo = pw.ForeignKeyField(RepoModel, default=None, null=True)
ssh_key = pw.CharField(default=None, null=True)
compression = pw.CharField(default='lz4')
exclude_patterns = pw.TextField(null=True)
exclude_if_present = pw.TextField(null=True)
schedule_mode = pw.CharField(default='off')
schedule_interval_hours = pw.IntegerField(default=3)
schedule_interval_minutes = pw.IntegerField(default=42)
schedule_fixed_hour = pw.IntegerField(default=3)
schedule_fixed_minute = pw.IntegerField(default=42)
validation_on = pw.BooleanField(default=True)
validation_weeks = pw.IntegerField(default=3)
prune_on = pw.BooleanField(default=False)
prune_hour = pw.IntegerField(default=2)
prune_day = pw.IntegerField(default=7)
prune_week = pw.IntegerField(default=4)
prune_month = pw.IntegerField(default=6)
prune_year = pw.IntegerField(default=2)
class Meta:
database = db
class SourceDirModel(peewee.Model):
class SourceDirModel(pw.Model):
"""A folder to be backed up, related to a Backup Configuration."""
dir = peewee.CharField()
config = peewee.ForeignKeyField(BackupProfileModel, default=1)
added_at = peewee.DateTimeField(default=datetime.utcnow)
dir = pw.CharField()
config = pw.ForeignKeyField(BackupProfileModel, default=1)
added_at = pw.DateTimeField(default=datetime.utcnow)
class Meta:
database = db
class SnapshotModel(peewee.Model):
class SnapshotModel(pw.Model):
"""A snapshot to a specific remote repository."""
snapshot_id = peewee.CharField(unique=True)
name = peewee.CharField()
repo = peewee.ForeignKeyField(RepoModel, backref='snapshots')
time = peewee.DateTimeField()
duration = peewee.FloatField(null=True)
size = peewee.IntegerField(null=True)
snapshot_id = pw.CharField(unique=True)
name = pw.CharField()
repo = pw.ForeignKeyField(RepoModel, backref='snapshots')
time = pw.DateTimeField()
duration = pw.FloatField(null=True)
size = pw.IntegerField(null=True)
def formatted_time(self):
return
@ -88,26 +97,35 @@ class SnapshotModel(peewee.Model):
database = db
class WifiSettingModel(peewee.Model):
class WifiSettingModel(pw.Model):
"""Save Wifi Settings"""
ssid = peewee.CharField()
last_connected = peewee.DateTimeField()
allowed = peewee.BooleanField(default=True)
profile = peewee.ForeignKeyField(BackupProfileModel, default=1)
ssid = pw.CharField()
last_connected = pw.DateTimeField()
allowed = pw.BooleanField(default=True)
profile = pw.ForeignKeyField(BackupProfileModel, default=1)
class Meta:
database = db
class EventLogModel(peewee.Model):
class EventLogModel(pw.Model):
"""Keep a log of background jobs."""
start_time = peewee.DateTimeField(default=datetime.now)
category = peewee.CharField()
subcommand = peewee.CharField(null=True)
message = peewee.CharField(null=True)
returncode = peewee.IntegerField(default=1)
start_time = pw.DateTimeField(default=datetime.now)
category = pw.CharField()
subcommand = pw.CharField(null=True)
message = pw.CharField(null=True)
returncode = pw.IntegerField(default=1)
params = JSONField(null=True)
profile = peewee.ForeignKeyField(BackupProfileModel, default=1)
profile = pw.ForeignKeyField(BackupProfileModel, default=1)
class Meta:
database = db
class SchemaVersion(pw.Model):
"""Keep DB version to apply the correct migrations."""
version = pw.IntegerField()
changed_at = pw.DateTimeField(default=datetime.now)
class Meta:
database = db
@ -115,16 +133,43 @@ class EventLogModel(peewee.Model):
class BackupProfileMixin:
"""Extend to support multiple profiles later."""
@property
def profile(self):
return BackupProfileModel.get(id=1)
def _apply_schema_update(current_schema, version_after, *operations):
with db.atomic():
migrate(*operations)
current_schema.version = version_after
current_schema.changed_at = datetime.now()
current_schema.save()
def init_db(con):
db.initialize(con)
db.connect()
db.create_tables([RepoModel, BackupProfileModel, SourceDirModel,
SnapshotModel, WifiSettingModel, EventLogModel])
SnapshotModel, WifiSettingModel, EventLogModel, SchemaVersion])
BackupProfileModel.get_or_create(id=1, name='Default')
# Migrations
# See http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#schema-migrations
current_schema, created = SchemaVersion.get_or_create(id=1, defaults={'version': SCHEMA_VERSION})
current_schema.save()
if created or current_schema.version == SCHEMA_VERSION:
return
else:
migrator = SqliteMigrator(con)
if current_schema.version < 3: # version 2 to 3
pass
if current_schema.version < 4: # version 3 to 4
_apply_schema_update(
current_schema, 4,
migrator.add_column(SnapshotModel._meta.table_name, 'duration', pw.FloatField(null=True)),
migrator.add_column(SnapshotModel._meta.table_name, 'size', pw.IntegerField(null=True))
)

View File

@ -4,7 +4,6 @@ from .views.main_window import MainWindow
from PyQt5.QtGui import QIcon
from .utils import get_asset
from .config import remove_config
from .borg_runner import BorgThread
@ -29,9 +28,6 @@ class TrayMenu(QSystemTrayIcon):
menu.addSeparator()
exit_action = menu.addAction("Factory Reset")
exit_action.triggered.connect(self.on_reset)
exit_action = menu.addAction("Exit")
exit_action.triggered.connect(self.on_exit_action)
@ -44,15 +40,11 @@ class TrayMenu(QSystemTrayIcon):
def on_exit_action(self):
self.app.quit()
def on_reset(self):
remove_config()
self.app.quit()
def on_user_click(self):
"""Adjust labels to reflect current status."""
if BorgThread.is_running():
self.status.setText('Backup in Progress')
self.create_action.setText('Cancel Backup')
else:
self.status.setText(self.app.scheduler.next_job)
self.status.setText(f'Next Task: {self.app.scheduler.next_job}')
self.create_action.setText('Backup Now')

View File

@ -49,7 +49,7 @@ def get_private_keys():
return available_private_keys
def prettyBytes(size):
def pretty_bytes(size):
"""from https://stackoverflow.com/questions/12523586/
python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778"""
if type(size) != int:

View File

@ -9,6 +9,42 @@
from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x02\x12\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x31\x34\x30\x38\x68\x36\x34\x30\x76\x2d\x31\x32\x38\x68\x2d\x36\
\x34\x30\x76\x31\x32\x38\x7a\x6d\x2d\x33\x38\x34\x2d\x35\x31\x32\
\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\
\x76\x31\x32\x38\x7a\x6d\x36\x34\x30\x2d\x35\x31\x32\x68\x33\x38\
\x34\x76\x2d\x31\x32\x38\x68\x2d\x33\x38\x34\x76\x31\x32\x38\x7a\
\x6d\x35\x31\x32\x20\x38\x33\x32\x76\x32\x35\x36\x71\x30\x20\x32\
\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\x68\x2d\
\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\x31\x39\
\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\x2d\x32\
\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\x31\x36\
\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\x31\x39\
\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\x71\x30\
\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\
\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\
\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\
\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\
\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\
\x31\x39\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\
\x71\x30\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\
\x31\x39\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\
\x35\x2d\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\
\x71\x30\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\
\x39\x68\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\
\x39\x74\x31\x39\x20\x34\x35\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\
\x3e\
\x00\x00\x01\x89\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
@ -46,40 +82,6 @@ qt_resource_data = b"\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x32\x38\x20\x31\
\x34\x30\x38\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\
\x30\x32\x34\x76\x31\x32\x38\x7a\x6d\x30\x2d\x35\x31\x32\x68\x31\
\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\
\x32\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\
\x30\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\
\x38\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\
\x20\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\
\x36\x38\x7a\x6d\x2d\x31\x35\x36\x38\x2d\x39\x36\x30\x68\x31\x30\
\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\x32\
\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\x30\
\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\
\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\
\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\
\x38\x7a\x6d\x30\x2d\x35\x31\x32\x71\x30\x2d\x34\x30\x2d\x32\x38\
\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\x20\x32\x38\
\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\x36\x38\x20\
\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\x38\x7a\x6d\
\x39\x36\x20\x38\x33\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\
\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\
\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\
\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\x32\x76\x33\x38\x34\
\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x35\x34\x34\x76\x34\x34\x38\x71\x30\x20\x31\x34\x2d\x39\x20\x32\
\x33\x74\x2d\x32\x33\x20\x39\x68\x2d\x33\x32\x30\x71\x2d\x31\x34\
@ -104,6 +106,40 @@ qt_resource_data = b"\
\x20\x33\x38\x35\x2e\x35\x20\x31\x30\x33\x20\x32\x37\x39\x2e\x35\
\x20\x32\x37\x39\x2e\x35\x20\x31\x30\x33\x20\x33\x38\x35\x2e\x35\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x01\xfb\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x32\x38\x20\x31\
\x34\x30\x38\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\
\x30\x32\x34\x76\x31\x32\x38\x7a\x6d\x30\x2d\x35\x31\x32\x68\x31\
\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\
\x32\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\
\x30\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\
\x38\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\
\x20\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\
\x36\x38\x7a\x6d\x2d\x31\x35\x36\x38\x2d\x39\x36\x30\x68\x31\x30\
\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\x76\x31\x32\
\x38\x7a\x6d\x31\x35\x36\x38\x20\x34\x34\x38\x71\x30\x2d\x34\x30\
\x2d\x32\x38\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\
\x20\x32\x38\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\
\x36\x38\x20\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\
\x38\x7a\x6d\x30\x2d\x35\x31\x32\x71\x30\x2d\x34\x30\x2d\x32\x38\
\x2d\x36\x38\x74\x2d\x36\x38\x2d\x32\x38\x2d\x36\x38\x20\x32\x38\
\x2d\x32\x38\x20\x36\x38\x20\x32\x38\x20\x36\x38\x20\x36\x38\x20\
\x32\x38\x20\x36\x38\x2d\x32\x38\x20\x32\x38\x2d\x36\x38\x7a\x6d\
\x39\x36\x20\x38\x33\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\
\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\
\x32\x76\x33\x38\x34\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\
\x68\x31\x37\x39\x32\x7a\x6d\x30\x2d\x35\x31\x32\x76\x33\x38\x34\
\x68\x2d\x31\x37\x39\x32\x76\x2d\x33\x38\x34\x68\x31\x37\x39\x32\
\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x03\x19\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
@ -156,42 +192,6 @@ qt_resource_data = b"\
\x31\x30\x20\x31\x30\x20\x32\x33\x20\x30\x20\x31\x38\x2d\x37\x35\
\x2e\x35\x20\x39\x33\x74\x2d\x39\x32\x2e\x35\x20\x37\x35\x7a\x22\
\x2f\x3e\x3c\x2f\x73\x76\x67\x3e\
\x00\x00\x02\x12\
\x3c\
\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
\x2d\x38\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
\x3d\x22\x31\x37\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
\x31\x37\x39\x32\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
\x20\x30\x20\x31\x37\x39\x32\x20\x31\x37\x39\x32\x22\x20\x78\x6d\
\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\
\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x30\x32\x34\x20\
\x31\x34\x30\x38\x68\x36\x34\x30\x76\x2d\x31\x32\x38\x68\x2d\x36\
\x34\x30\x76\x31\x32\x38\x7a\x6d\x2d\x33\x38\x34\x2d\x35\x31\x32\
\x68\x31\x30\x32\x34\x76\x2d\x31\x32\x38\x68\x2d\x31\x30\x32\x34\
\x76\x31\x32\x38\x7a\x6d\x36\x34\x30\x2d\x35\x31\x32\x68\x33\x38\
\x34\x76\x2d\x31\x32\x38\x68\x2d\x33\x38\x34\x76\x31\x32\x38\x7a\
\x6d\x35\x31\x32\x20\x38\x33\x32\x76\x32\x35\x36\x71\x30\x20\x32\
\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\x68\x2d\
\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\x31\x39\
\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\x2d\x32\
\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\x31\x36\
\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\x31\x39\
\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\x71\x30\
\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\x31\x39\
\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\x35\x2d\
\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\x71\x30\
\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\x39\x68\
\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\x39\x74\
\x31\x39\x20\x34\x35\x7a\x6d\x30\x2d\x35\x31\x32\x76\x32\x35\x36\
\x71\x30\x20\x32\x36\x2d\x31\x39\x20\x34\x35\x74\x2d\x34\x35\x20\
\x31\x39\x68\x2d\x31\x36\x36\x34\x71\x2d\x32\x36\x20\x30\x2d\x34\
\x35\x2d\x31\x39\x74\x2d\x31\x39\x2d\x34\x35\x76\x2d\x32\x35\x36\
\x71\x30\x2d\x32\x36\x20\x31\x39\x2d\x34\x35\x74\x34\x35\x2d\x31\
\x39\x68\x31\x36\x36\x34\x71\x32\x36\x20\x30\x20\x34\x35\x20\x31\
\x39\x74\x31\x39\x20\x34\x35\x7a\x22\x2f\x3e\x3c\x2f\x73\x76\x67\
\x3e\
"
qt_resource_name = b"\
@ -199,37 +199,37 @@ qt_resource_name = b"\
\x00\x6f\xa6\x53\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x73\
\x00\x09\
\x0a\x26\xaf\xc7\
\x00\x74\
\x00\x61\x00\x73\x00\x6b\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x12\
\x05\x98\xe2\x07\
\x00\x77\
\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x65\x00\x73\x00\x74\x00\x6f\x00\x72\x00\x65\x00\x2e\x00\x73\x00\x76\
\x00\x67\
\x00\x0a\
\x0c\xca\x63\xe7\
\x00\x73\
\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x0b\
\x0f\x16\x31\xe7\
\x00\x63\
\x00\x6c\x00\x6f\x00\x63\x00\x6b\x00\x2d\x00\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x0a\
\x0c\xca\x63\xe7\
\x00\x73\
\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x08\
\x0f\xcc\x55\x67\
\x00\x77\
\x00\x69\x00\x66\x00\x69\x00\x2e\x00\x73\x00\x76\x00\x67\
\x00\x09\
\x0a\x26\xaf\xc7\
\x00\x74\
\x00\x61\x00\x73\x00\x6b\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x02\x16\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x08\xa8\
\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x01\x8d\
\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x03\x8c\
\x00\x00\x00\x70\x00\x00\x00\x00\x00\x01\x00\x00\x05\x8b\
\x00\x00\x00\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x05\xa2\
\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa3\
\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00\x07\xa1\
"
qt_resource_struct_v2 = b"\
@ -237,15 +237,15 @@ qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x02\x16\
\x00\x00\x01\x66\xbf\xa1\xb1\x2c\
\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x08\xa8\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x66\xbf\x9f\xde\x3e\
\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x01\x8d\
\x00\x00\x00\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x05\xa2\
\x00\x00\x01\x66\xbf\xa1\x0f\xda\
\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x03\x8c\
\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa3\
\x00\x00\x01\x66\xbf\x9d\x97\x00\
\x00\x00\x00\x70\x00\x00\x00\x00\x00\x01\x00\x00\x05\x8b\
\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00\x07\xa1\
\x00\x00\x01\x66\xbf\x9f\x19\x44\
"

View File

@ -3,7 +3,7 @@ from PyQt5 import uic, QtCore
from ..models import RepoModel, SnapshotModel, BackupProfileMixin
from .repo_add import AddRepoWindow, ExistingRepoWindow
from ..utils import prettyBytes, get_private_keys, get_asset, keyring
from ..utils import pretty_bytes, get_private_keys, get_asset, keyring
from .ssh_add import SSHAddWindow
uifile = get_asset('UI/repotab.ui')
@ -40,9 +40,9 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
def init_repo_stats(self):
repo = self.profile.repo
self.sizeCompressed.setText(prettyBytes(repo.unique_csize))
self.sizeDeduplicated.setText(prettyBytes(repo.unique_size))
self.sizeOriginal.setText(prettyBytes(repo.total_size))
self.sizeCompressed.setText(pretty_bytes(repo.unique_csize))
self.sizeDeduplicated.setText(pretty_bytes(repo.unique_size))
self.sizeOriginal.setText(pretty_bytes(repo.total_size))
self.repoEncryption.setText(str(repo.encryption))
self.repo_changed.emit(repo.id)

View File

@ -1,8 +1,9 @@
from datetime import timedelta
from PyQt5 import uic
from PyQt5.QtWidgets import QFileDialog, QTableWidgetItem, QTableView, QHeaderView
from ..borg_runner import BorgThread
from ..utils import get_asset, keyring
from ..utils import get_asset, keyring, pretty_bytes
from ..models import BackupProfileMixin
uifile = get_asset('UI/snapshottab.ui')
@ -18,6 +19,8 @@ class SnapshotTab(SnapshotBase, SnapshotUI, BackupProfileMixin):
header.setVisible(True)
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
self.snapshotTable.setSelectionBehavior(QTableView.SelectRows)
self.snapshotTable.setEditTriggers(QTableView.NoEditTriggers)
@ -39,8 +42,14 @@ class SnapshotTab(SnapshotBase, SnapshotUI, BackupProfileMixin):
for row, snapshot in enumerate(snapshots):
self.snapshotTable.insertRow(row)
self.snapshotTable.setItem(row, 0, QTableWidgetItem(snapshot.name))
self.snapshotTable.setItem(row, 1, QTableWidgetItem(pretty_bytes(snapshot.size)))
if snapshot.duration:
formatted_duration = str(timedelta(seconds=round(snapshot.duration)))
else:
formatted_duration = 'N/A'
self.snapshotTable.setItem(row, 2, QTableWidgetItem(formatted_duration))
formatted_time = snapshot.time.strftime('%Y-%m-%d %H:%M')
self.snapshotTable.setItem(row, 1, QTableWidgetItem(formatted_time))
self.snapshotTable.setItem(row, 3, QTableWidgetItem(formatted_time))
self.snapshotTable.setRowCount(len(snapshots))
def snapshot_mount(self):
@ -67,7 +76,8 @@ class SnapshotTab(SnapshotBase, SnapshotUI, BackupProfileMixin):
self.set_status('Mounting snapshot into folder...')
params = {'password': keyring.get_password("vorta-repo", profile.repo.url)}
thread = BorgThread(self, cmd, params, parent=self)
self.snapshotMountButton.setEnabled(False)
thread = BorgThread(cmd, params, parent=self)
thread.updated.connect(self.mount_update_log)
thread.result.connect(self.mount_get_result)
thread.start()
@ -76,5 +86,7 @@ class SnapshotTab(SnapshotBase, SnapshotUI, BackupProfileMixin):
self.mountErrors.setText(text)
def mount_get_result(self, result):
self.snapshotMountButton.setEnabled(True)
if result['returncode'] == 0:
self.set_status('Mounted successfully.')

View File

@ -1,13 +1,14 @@
import pytest
from datetime import datetime as dt, date, time
from PyQt5 import QtCore
from vorta.views.schedule_tab import ScheduleTab
from .fixtures import app, main
from .fixtures import *
def test_schedule_tab(main, qtbot):
tab = ScheduleTab(main.scheduleTabSlot)
qtbot.addWidget(tab)
# qtbot.addWidget(tab)
qtbot.mouseClick(tab.scheduleApplyButton, QtCore.Qt.LeftButton)
assert tab.nextBackupDateTimeLabel.text() == 'Manual Backups'
@ -26,4 +27,3 @@ def test_schedule_tab(main, qtbot):
qtbot.mouseClick(tab.scheduleApplyButton, QtCore.Qt.LeftButton)
next_backup = dt.combine(date.today(), time(23, 59))
assert tab.nextBackupDateTimeLabel.text() == next_backup.strftime('%Y-%m-%d %H:%M')