Skip to content

Commit

Permalink
Add a dev mode that allows for local storing of config files and logs (
Browse files Browse the repository at this point in the history
…borgbase#1682)

Allows vorta to be called with the command-line flag `--development` or `-D` that will make it use a directory in the project tree to store all the settings, logs, and cache. This default directory will be called `.dev_config` and placed in the projects root.
Also allows for a custom directory path allowing for multiple "configuration" folders at once.
This can be used to prevent the vorta instance that a developer is working on from accessing the configuration files that they have set up for their personal backups.

* .gitignore : Add `.dev_config`.

* src/vorta/utils.py (parse_args): Add `--development` flag. The default will be `DEFAULT_DIR_FLAG`.

* src/vorta/utils.py : Add `DEFAULT_DIR_FLAG`.

* src/vorta/config.py : Add methods for populating the config directories exposed by this module.

* src/vorta/__main__.py (main): Handle `--development` flag and update config directories if its specified.

* Access config constants through the `config` module instead of importing them directly with `from .config import`.

---------
Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com>
  • Loading branch information
ratchek authored and DaffyTheDuck committed Jun 14, 2023
1 parent 59b7802 commit 52e8310
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 27 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ env
venv
.env
.venv
# dirs created by the --development option
.dev_config/
# Avoid adding translations of source language
# Files are still used by Transifex
src/vorta/i18n/ts/vorta.en.ts
Expand Down
18 changes: 15 additions & 3 deletions src/vorta/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

from peewee import SqliteDatabase

# Need to import config as a whole module instead of individual variables
# because we will be overriding the modules variables
from vorta import config
from vorta._version import __version__
from vorta.config import SETTINGS_DIR
from vorta.i18n import trans_late, translate
from vorta.log import init_logger, logger
from vorta.store.connection import init_db
from vorta.updater import get_updater
from vorta.utils import parse_args
from vorta.utils import DEFAULT_DIR_FLAG, parse_args


def main():
Expand Down Expand Up @@ -48,6 +50,7 @@ def exception_handler(type, value, tb):

want_version = getattr(args, 'version', False)
want_background = getattr(args, 'daemonize', False)
want_development = getattr(args, 'development', False)

if want_version:
print(f"Vorta {__version__}") # noqa: T201
Expand All @@ -57,11 +60,20 @@ def exception_handler(type, value, tb):
if os.fork():
sys.exit()

if want_development:
# if we're using the default dev dir
if want_development is DEFAULT_DIR_FLAG:
config.init_dev_mode(config.default_dev_dir())
else:
# if we're not using the default dev dir and
# instead we're using whatever dir is passed as an argument
config.init_dev_mode(want_development)

init_logger(background=want_background)

# Init database
sqlite_db = SqliteDatabase(
SETTINGS_DIR / 'settings.db',
config.SETTINGS_DIR / 'settings.db',
pragmas={
'journal_mode': 'wal',
},
Expand Down
12 changes: 8 additions & 4 deletions src/vorta/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from PyQt6 import QtCore
from PyQt6.QtWidgets import QMessageBox

from vorta import config
from vorta.borg.break_lock import BorgBreakJob
from vorta.borg.create import BorgCreateJob
from vorta.borg.jobs_manager import JobsManager
from vorta.borg.version import BorgVersionJob
from vorta.config import LOG_DIR, PROFILE_BOOTSTRAP_FILE, TEMP_DIR
from vorta.i18n import init_translations, translate
from vorta.notifications import VortaNotifications
from vorta.profile_export import ProfileExport
Expand All @@ -25,7 +25,7 @@

logger = logging.getLogger(__name__)

APP_ID = TEMP_DIR / "socket"
APP_ID = config.TEMP_DIR / "socket"


class VortaApp(QtSingleApplication):
Expand Down Expand Up @@ -261,7 +261,11 @@ def break_lock(self, profile):
job = BorgBreakJob(params['cmd'], params)
self.jobs_manager.add_job(job)

def bootstrap_profile(self, bootstrap_file=PROFILE_BOOTSTRAP_FILE):
def bootstrap_profile(self, bootstrap_file=None):
# Necessary to dynamically load the variable from config during runtime
# Check out pull request for #1682 for context
bootstrap_file = bootstrap_file or config.PROFILE_BOOTSTRAP_FILE

"""
Make sure there is at least one profile when first starting Vorta.
Will either import a profile placed in ~/.vorta-init.json
Expand Down Expand Up @@ -334,7 +338,7 @@ def check_failed_response(self, result: Dict[str, Any]):
msg.setIcon(QMessageBox.Icon.Warning)
text = translate(
'VortaApp', 'Borg exited with warning status (rc 1). See the <a href="{0}">logs</a> for details.'
).format(LOG_DIR.as_uri())
).format(config.LOG_DIR.as_uri())
infotext = error_message
elif returncode > 128:
# 128+N - killed by signal N (e.g. 137 == kill -9)
Expand Down
4 changes: 2 additions & 2 deletions src/vorta/borg/check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict

from vorta.config import LOG_DIR
from vorta import config
from vorta.i18n import translate
from vorta.utils import borg_compat

Expand All @@ -27,7 +27,7 @@ def finished_event(self, result: Dict[str, Any]):
self.app.backup_progress_event.emit(
f"[{self.params['profile_name']}] "
+ translate('RepoCheckJob', 'Repo check failed. See the <a href="{0}">logs</a> for details.').format(
LOG_DIR.as_uri()
config.LOG_DIR.as_uri()
)
)
self.app.check_failed_event.emit(result)
Expand Down
4 changes: 2 additions & 2 deletions src/vorta/borg/compact.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict

from vorta.config import LOG_DIR
from vorta import config
from vorta.i18n import trans_late, translate
from vorta.utils import borg_compat

Expand Down Expand Up @@ -30,7 +30,7 @@ def finished_event(self, result: Dict[str, Any]):
f"[{self.params['profile_name']}] "
+ translate(
'BorgCompactJob', 'Errors during compaction. See the <a href="{0}">logs</a> for details.'
).format(LOG_DIR.as_uri())
).format(config.LOG_DIR.as_uri())
)
else:
self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Compaction completed.')}")
Expand Down
4 changes: 2 additions & 2 deletions src/vorta/borg/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import tempfile
from datetime import datetime as dt

from vorta.config import LOG_DIR
from vorta import config
from vorta.i18n import trans_late, translate
from vorta.store.models import (
ArchiveModel,
Expand Down Expand Up @@ -46,7 +46,7 @@ def process_result(self, result):
+ translate(
'BorgCreateJob',
'Backup finished with warnings. See the <a href="{0}">logs</a> for details.',
).format(LOG_DIR.as_uri())
).format(config.LOG_DIR.as_uri())
)
else:
self.app.backup_log_event.emit('', {})
Expand Down
61 changes: 52 additions & 9 deletions src/vorta/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,57 @@
APP_NAME = 'Vorta'
APP_AUTHOR = 'BorgBase'
APP_ID_DARWIN = 'com.borgbase.client.macos'
dirs = platformdirs.PlatformDirs(APP_NAME, APP_AUTHOR)
SETTINGS_DIR = dirs.user_data_path
LOG_DIR = dirs.user_log_path
CACHE_DIR = dirs.user_cache_path
TEMP_DIR = CACHE_DIR / "tmp"
PROFILE_BOOTSTRAP_FILE = Path.home() / '.vorta-init.json'
SETTINGS_DIR = None
LOG_DIR = None
CACHE_DIR = None
TEMP_DIR = None
PROFILE_BOOTSTRAP_FILE = None


# ensure directories exist
for dir in (SETTINGS_DIR, LOG_DIR, CACHE_DIR, TEMP_DIR):
dir.mkdir(parents=True, exist_ok=True)
def default_dev_dir() -> Path:
"""Returns a default dir for config files in the project's main folder"""
return Path(__file__).parent.parent.parent / '.dev_config'


def init_from_platformdirs():
"""Initializes config dirs for system-wide use"""
dirs = platformdirs.PlatformDirs(APP_NAME, APP_AUTHOR)
init(dirs.user_data_path, dirs.user_log_path, dirs.user_cache_path, dirs.user_cache_path / 'tmp', Path.home())


def init_dev_mode(dir: Path):
"""Initializes config dirs for local use inside provided dir"""
dir_full_path = Path(dir).resolve()
init(
dir_full_path / 'settings',
dir_full_path / 'logs',
dir_full_path / 'cache',
dir_full_path / 'tmp',
dir_full_path,
)


def init(settings: Path, logs: Path, cache: Path, tmp: Path, bootstrap: Path):
"""Initializes config directories with provided paths"""
global SETTINGS_DIR
global LOG_DIR
global CACHE_DIR
global TEMP_DIR
global PROFILE_BOOTSTRAP_FILE
SETTINGS_DIR = settings
LOG_DIR = logs
CACHE_DIR = cache
TEMP_DIR = tmp
PROFILE_BOOTSTRAP_FILE = bootstrap / '.vorta-init.json'
ensure_dirs()


def ensure_dirs():
"""Creates config dirs and parent dirs if they don't exist"""
# ensure directories exist
for dir in (SETTINGS_DIR, LOG_DIR, CACHE_DIR, TEMP_DIR):
dir.mkdir(parents=True, exist_ok=True)


# Make sure that the config values are valid
init_from_platformdirs()
4 changes: 2 additions & 2 deletions src/vorta/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import logging
from logging.handlers import TimedRotatingFileHandler

from .config import LOG_DIR
from vorta import config

logger = logging.getLogger()

Expand All @@ -23,7 +23,7 @@ def init_logger(background=False):
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# create handlers
fh = TimedRotatingFileHandler(LOG_DIR / 'vorta.log', when='d', interval=1, backupCount=5)
fh = TimedRotatingFileHandler(config.LOG_DIR / 'vorta.log', when='d', interval=1, backupCount=5)
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
Expand Down
20 changes: 19 additions & 1 deletion src/vorta/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
from vorta.log import logger
from vorta.network_status.abc import NetworkStatusMonitor

# Used to store whether a user wanted to override the
# default directory for the --development flag
DEFAULT_DIR_FLAG = object()

borg_compat = BorgCompatibility()
_network_status_monitor = None

Expand Down Expand Up @@ -353,7 +357,21 @@ def parse_args():
help='Create a backup in the background using the given profile. '
'Vorta must already be running for this to work.',
)

# the "development" attribute will be None if the flag is not called
# if the flag is called without an extra argument, the "development" attribute
# will be set to the value of DEFAULT_DIR_FLAG.
# if the flag is called with an extra argument, the "development" attribute
# will be set to that argument
parser.add_argument(
'--development',
'-D',
nargs='?',
const=DEFAULT_DIR_FLAG,
metavar="CONFIG_DIRECTORY",
help='Start vorta in a local development environment. '
'All log, config, cache, and temp files will be stored within the project tree. '
'You can follow this flag with an optional path and it will store the files in the provided location.',
)
return parser.parse_known_args()[0]


Expand Down
5 changes: 3 additions & 2 deletions src/vorta/views/misc_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
QSpacerItem,
)

from vorta import config
from vorta._version import __version__
from vorta.config import LOG_DIR
from vorta.i18n import translate
from vorta.store.models import BackupProfileMixin, SettingsModel
from vorta.store.settings import get_misc_settings
Expand All @@ -37,7 +37,8 @@ def __init__(self, parent=None):

self.versionLabel.setText(__version__)
self.logLink.setText(
f'<a href="file://{LOG_DIR}"><span style="text-decoration:' 'underline; color:#0984e3;">Log</span></a>'
f'<a href="file://{config.LOG_DIR}"><span style="text-decoration:'
'underline; color:#0984e3;">Log</span></a>'
)

self.checkboxLayout = QFormLayout(self.frameSettings)
Expand Down

0 comments on commit 52e8310

Please sign in to comment.