vorta/src/vorta/profile_export.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

181 lines
6.7 KiB
Python
Raw Normal View History

import datetime
import json
from json import JSONDecodeError
from playhouse.shortcuts import dict_to_model, model_to_dict
from vorta.keyring.abc import VortaKeyring
from vorta.store.connection import DB, SCHEMA_VERSION, init_db
from vorta.store.models import (
BackupProfileModel,
RepoModel,
SchemaVersion,
SettingsModel,
SourceFileModel,
WifiSettingModel,
2022-08-15 05:21:14 +00:00
)
class ProfileExport:
def __init__(self, profile_dict):
self._profile_dict = profile_dict
@property
def id(self):
return self._profile_dict['id']
@property
def name(self):
return self._profile_dict['name']
@property
def schema_version(self):
return self._profile_dict['SchemaVersion']['version']
@property
def repo_url(self):
if (
'repo' in self._profile_dict
and isinstance(self._profile_dict['repo'], dict)
and 'url' in self._profile_dict['repo']
):
return self._profile_dict['repo']['url']
return None
@property
def repo_password(self):
return self._profile_dict['password'] if 'password' in self._profile_dict else None
@repo_password.setter
def repo_password(self, password):
self._profile_dict['password'] = password
@classmethod
def from_db(cls, profile, store_password=True, include_settings=True):
profile_dict = model_to_dict(profile, exclude=[RepoModel.id]) # Have to retain profile ID
keyring = VortaKeyring.get_keyring()
if store_password:
profile_dict['password'] = keyring.get_password('vorta-repo', profile.repo.url)
# For all below, exclude ids to prevent collisions. DB will automatically reassign ids
# Add SourceFileModel
profile_dict['SourceFileModel'] = [
model_to_dict(source, recurse=False, exclude=[SourceFileModel.id])
for source in SourceFileModel.select().where(SourceFileModel.profile == profile)
]
# Add SchemaVersion
profile_dict['SchemaVersion'] = model_to_dict(SchemaVersion.get(id=1))
if include_settings:
# Add WifiSettingModel
profile_dict['WifiSettingModel'] = [
model_to_dict(wifi, recurse=False)
for wifi in WifiSettingModel.select().where(WifiSettingModel.profile == profile.id)
]
# Add SettingsModel
profile_dict['SettingsModel'] = [model_to_dict(s, exclude=[SettingsModel.id]) for s in SettingsModel]
return ProfileExport(profile_dict)
def to_db(self, overwrite_profile=False, overwrite_settings=True):
profile_schema = self._profile_dict['SchemaVersion']['version']
keyring = VortaKeyring.get_keyring()
if SCHEMA_VERSION < profile_schema:
raise VersionException()
elif SCHEMA_VERSION > profile_schema:
# Add model upgrading code here, only needed if not adding columns
if profile_schema < 16:
for sourcedir in self._profile_dict['SourceFileModel']:
sourcedir['dir_files_count'] = -1
sourcedir['dir_size'] = -1
sourcedir['path_isdir'] = False
existing_profile = None
if overwrite_profile:
existing_profile = BackupProfileModel.get_or_none(BackupProfileModel.name == self.name)
if existing_profile:
self._profile_dict['id'] = existing_profile.id
if not overwrite_profile or not existing_profile:
# Guarantee uniqueness of ids
while BackupProfileModel.get_or_none(BackupProfileModel.id == self.id) is not None:
self._profile_dict['id'] += 1
# Add suffix incase names are the same
if BackupProfileModel.get_or_none(BackupProfileModel.name == self.name) is not None:
suffix = 1
while BackupProfileModel.get_or_none(BackupProfileModel.name == f"{self.name}-{suffix}") is not None:
suffix += 1
self._profile_dict['name'] = f"{self.name}-{suffix}"
# Load existing repo or restore it
if self._profile_dict['repo']:
repo = RepoModel.get_or_none(RepoModel.url == self.repo_url)
if repo is None:
# Load repo from export
repo = dict_to_model(RepoModel, self._profile_dict['repo'])
repo.save(force_insert=True)
self._profile_dict['repo'] = model_to_dict(repo)
if self.repo_password:
keyring.set_password('vorta-repo', self.repo_url, self.repo_password)
del self._profile_dict['password']
# Delete and recreate the tables to clear them
if overwrite_settings:
DB.drop_tables([SettingsModel, WifiSettingModel])
DB.create_tables([SettingsModel, WifiSettingModel])
SettingsModel.insert_many(self._profile_dict['SettingsModel']).execute()
WifiSettingModel.insert_many(self._profile_dict['WifiSettingModel']).execute()
# Set the profile ids to be match new profile
for source in self._profile_dict['SourceFileModel']:
source['profile'] = self.id
SourceFileModel.insert_many(self._profile_dict['SourceFileModel']).execute()
# Delete added dictionaries to make it match BackupProfileModel
del self._profile_dict['SettingsModel']
del self._profile_dict['SourceFileModel']
del self._profile_dict['WifiSettingModel']
del self._profile_dict['SchemaVersion']
# dict to profile
new_profile = dict_to_model(BackupProfileModel, self._profile_dict)
if overwrite_profile and existing_profile:
force_insert = False
else:
force_insert = True
new_profile.save(force_insert=force_insert)
init_db() # rerun db init code to perform the same operations on the new as as on application boot
return new_profile
@classmethod
def from_json(cls, filename):
with open(filename, 'r') as file:
try:
profile_export = ProfileExport(json.loads(file.read()))
except JSONDecodeError as exception:
raise ImportFailedException(
'This file does not contain valid JSON: {}'.format(str(exception))
) from exception
return profile_export
def to_json(self):
return json.dumps(self._profile_dict, default=self._converter, indent=4)
@staticmethod
def _converter(obj):
if isinstance(obj, datetime.datetime):
return obj.__str__()
class VersionException(Exception):
"""For when current_version < export_version. Should only occur if downgrading"""
2022-08-15 05:21:14 +00:00
pass
class ImportFailedException(Exception):
"""Raised when a profile could not be imported."""
2022-08-15 05:21:14 +00:00
pass