Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use platformdirs #7300

Merged
merged 20 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@

@pytest.fixture(autouse=True)
def clean_env(tmpdir_factory, monkeypatch):
# avoid that we access / modify the user's normal .config / .cache directory:
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmpdir_factory.mktemp("xdg-config-home")))
monkeypatch.setenv("XDG_CACHE_HOME", str(tmpdir_factory.mktemp("xdg-cache-home")))
# also avoid to use anything from the outside environment:
keys = [key for key in os.environ if key.startswith("BORG_") and key not in ("BORG_FUSE_IMPL",)]
for key in keys:
monkeypatch.delenv(key, raising=False)
# avoid that we access / modify the user's normal .config / .cache directory:
monkeypatch.setenv("BORG_BASE_DIR", str(tmpdir_factory.mktemp("borg-base-dir")))
# Speed up tests
monkeypatch.setenv("BORG_TESTONLY_WEAKEN_KDF", "1")

Expand Down
2 changes: 1 addition & 1 deletion scripts/msys2-install-deps
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,python,cython,python-setuptools,python-wheel,python-pkgconfig,python-packaging,python-msgpack,python-argon2_cffi,python-pip}
pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-pkgconfig,python-packaging,python-pip}
pip install pyinstaller

if [ "$1" = "development" ]; then
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ setup_requires =
install_requires =
msgpack >=1.0.3, <=1.0.4
packaging
platformdirs >=3.0.0, <4.0.0
argon2-cffi
tests_require =
pytest
Expand Down
2 changes: 1 addition & 1 deletion src/borg/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .checks import check_extension_modules, check_python
from .datastruct import StableDict, Buffer, EfficientCollectionQueue
from .errors import Error, ErrorWithTraceback, IntegrityError, DecompressionError
from .fs import ensure_dir, get_security_dir, get_keys_dir, get_base_dir, get_cache_dir, get_config_dir
from .fs import ensure_dir, get_security_dir, get_keys_dir, get_base_dir, join_base_dir, get_cache_dir, get_config_dir
from .fs import dir_is_tagged, dir_is_cachedir, make_path_safe, scandir_inorder
from .fs import secure_erase, safe_unlink, dash_open, os_open, os_stat, umount
from .fs import O_, flags_root, flags_dir, flags_special_follow, flags_special, flags_base, flags_normal, flags_noatime
Expand Down
81 changes: 54 additions & 27 deletions src/borg/helpers/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import sys
import textwrap

import platformdirs

from .errors import Error

from .process import prepare_subprocess_env
Expand Down Expand Up @@ -40,55 +42,73 @@ def ensure_dir(path, mode=stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO, pretty_dea
raise


def get_base_dir():
def get_base_dir(*, legacy=False):
"""Get home directory / base directory for borg:

- BORG_BASE_DIR, if set
- HOME, if set
- ~$USER, if USER is set
- ~
"""
base_dir = os.environ.get("BORG_BASE_DIR") or os.environ.get("HOME")
# os.path.expanduser() behaves differently for '~' and '~someuser' as
# parameters: when called with an explicit username, the possibly set
# environment variable HOME is no longer respected. So we have to check if
# it is set and only expand the user's home directory if HOME is unset.
if not base_dir:
base_dir = os.path.expanduser("~%s" % os.environ.get("USER", ""))
if legacy:
base_dir = os.environ.get("BORG_BASE_DIR") or os.environ.get("HOME")
# os.path.expanduser() behaves differently for '~' and '~someuser' as
# parameters: when called with an explicit username, the possibly set
# environment variable HOME is no longer respected. So we have to check if
# it is set and only expand the user's home directory if HOME is unset.
if not base_dir:
base_dir = os.path.expanduser("~%s" % os.environ.get("USER", ""))
else:
# we only care for BORG_BASE_DIR here, as it can be used to override the base dir
# and not use any more or less platform specific way to determine the base dir.
base_dir = os.environ.get("BORG_BASE_DIR")
return base_dir


def get_keys_dir():
def join_base_dir(*paths, **kw):
legacy = kw.get("legacy", True)
base_dir = get_base_dir(legacy=legacy)
return None if base_dir is None else os.path.join(base_dir, *paths)


def get_keys_dir(*, legacy=False):
"""Determine where to repository keys and cache"""
keys_dir = os.environ.get("BORG_KEYS_DIR")
if keys_dir is None:
# note: do not just give this as default to the environment.get(), see issue #5979.
keys_dir = os.path.join(get_config_dir(), "keys")
keys_dir = os.path.join(get_config_dir(legacy=legacy), "keys")
ensure_dir(keys_dir)
return keys_dir


def get_security_dir(repository_id=None):
def get_security_dir(repository_id=None, *, legacy=False):
"""Determine where to store local security information."""
security_dir = os.environ.get("BORG_SECURITY_DIR")
if security_dir is None:
# note: do not just give this as default to the environment.get(), see issue #5979.
security_dir = os.path.join(get_config_dir(), "security")
security_dir = os.path.join(get_config_dir(legacy=legacy), "security")
if repository_id:
security_dir = os.path.join(security_dir, repository_id)
ensure_dir(security_dir)
return security_dir


def get_cache_dir():
def get_cache_dir(*, legacy=False):
"""Determine where to repository keys and cache"""
# Get cache home path
cache_home = os.path.join(get_base_dir(), ".cache")
# Try to use XDG_CACHE_HOME instead if BORG_BASE_DIR isn't explicitly set
if not os.environ.get("BORG_BASE_DIR"):
cache_home = os.environ.get("XDG_CACHE_HOME", cache_home)
# Use BORG_CACHE_DIR if set, otherwise assemble final path from cache home path
cache_dir = os.environ.get("BORG_CACHE_DIR", os.path.join(cache_home, "borg"))

if legacy:
# Get cache home path
cache_home = join_base_dir(".cache", legacy=legacy)
# Try to use XDG_CACHE_HOME instead if BORG_BASE_DIR isn't explicitly set
if not os.environ.get("BORG_BASE_DIR"):
cache_home = os.environ.get("XDG_CACHE_HOME", cache_home)
# Use BORG_CACHE_DIR if set, otherwise assemble final path from cache home path
cache_dir = os.environ.get("BORG_CACHE_DIR", os.path.join(cache_home, "borg"))
else:
cache_dir = os.environ.get(
"BORG_CACHE_DIR", join_base_dir(".cache", legacy=legacy) or platformdirs.user_cache_dir("borg")
)

# Create path if it doesn't exist yet
ensure_dir(cache_dir)
cache_tag_fn = os.path.join(cache_dir, CACHE_TAG_NAME)
Expand All @@ -110,15 +130,22 @@ def get_cache_dir():
return cache_dir


def get_config_dir():
def get_config_dir(*, legacy=False):
"""Determine where to store whole config"""

# Get config home path
config_home = os.path.join(get_base_dir(), ".config")
# Try to use XDG_CONFIG_HOME instead if BORG_BASE_DIR isn't explicitly set
if not os.environ.get("BORG_BASE_DIR"):
config_home = os.environ.get("XDG_CONFIG_HOME", config_home)
# Use BORG_CONFIG_DIR if set, otherwise assemble final path from config home path
config_dir = os.environ.get("BORG_CONFIG_DIR", os.path.join(config_home, "borg"))
if legacy:
config_home = join_base_dir(".config", legacy=legacy)
# Try to use XDG_CONFIG_HOME instead if BORG_BASE_DIR isn't explicitly set
if not os.environ.get("BORG_BASE_DIR"):
config_home = os.environ.get("XDG_CONFIG_HOME", config_home)
# Use BORG_CONFIG_DIR if set, otherwise assemble final path from config home path
config_dir = os.environ.get("BORG_CONFIG_DIR", os.path.join(config_home, "borg"))
else:
config_dir = os.environ.get(
"BORG_CONFIG_DIR", join_base_dir(".config", legacy=legacy) or platformdirs.user_config_dir("borg")
)

# Create path if it doesn't exist yet
ensure_dir(config_dir)
return config_dir
Expand Down
162 changes: 126 additions & 36 deletions src/borg/testsuite/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from ..helpers import safe_unlink
from ..helpers import text_to_json, binary_to_json
from ..helpers.passphrase import Passphrase, PasswordRetriesExceeded
from ..platform import is_cygwin
from ..platform import is_cygwin, is_win32, is_darwin

from . import BaseTestCase, FakeInputs, are_hardlinks_supported

Expand Down Expand Up @@ -584,60 +584,150 @@ def test_get_base_dir(monkeypatch):
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
monkeypatch.delenv("HOME", raising=False)
monkeypatch.delenv("USER", raising=False)
assert get_base_dir() == os.path.expanduser("~")
assert get_base_dir(legacy=True) == os.path.expanduser("~")
monkeypatch.setenv("USER", "root")
assert get_base_dir() == os.path.expanduser("~root")
assert get_base_dir(legacy=True) == os.path.expanduser("~root")
monkeypatch.setenv("HOME", "/var/tmp/home")
assert get_base_dir() == "/var/tmp/home"
assert get_base_dir(legacy=True) == "/var/tmp/home"
monkeypatch.setenv("BORG_BASE_DIR", "/var/tmp/base")
assert get_base_dir() == "/var/tmp/base"
assert get_base_dir(legacy=True) == "/var/tmp/base"
# non-legacy is much easier:
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
assert get_base_dir(legacy=False) is None
monkeypatch.setenv("BORG_BASE_DIR", "/var/tmp/base")
assert get_base_dir(legacy=False) == "/var/tmp/base"


def test_get_base_dir_compat(monkeypatch):
"""test that it works the same for legacy and for non-legacy implementation"""
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
# old way: if BORG_BASE_DIR is not set, make something up with HOME/USER/~
# new way: if BORG_BASE_DIR is not set, return None and let caller deal with it.
assert get_base_dir(legacy=False) is None
# new and old way: BORG_BASE_DIR overrides all other "base path determination".
monkeypatch.setenv("BORG_BASE_DIR", "/var/tmp/base")
assert get_base_dir(legacy=False) == get_base_dir(legacy=True)


def test_get_config_dir(monkeypatch):
"""test that get_config_dir respects environment"""
monkeypatch.delenv("BORG_CONFIG_DIR", raising=False)
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
assert get_config_dir() == os.path.join(os.path.expanduser("~"), ".config", "borg")
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_config_dir() == os.path.join("/var/tmp/.config", "borg")
monkeypatch.setenv("BORG_CONFIG_DIR", "/var/tmp")
assert get_config_dir() == "/var/tmp"
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
home_dir = os.path.expanduser("~")
if is_win32:
monkeypatch.delenv("BORG_CONFIG_DIR", raising=False)
assert get_config_dir() == os.path.join(home_dir, "AppData", "Local", "borg", "borg")
monkeypatch.setenv("BORG_CONFIG_DIR", home_dir)
assert get_config_dir() == home_dir
elif is_darwin:
monkeypatch.delenv("BORG_CONFIG_DIR", raising=False)
assert get_config_dir() == os.path.join(home_dir, "Library", "Application Support", "borg")
monkeypatch.setenv("BORG_CONFIG_DIR", "/var/tmp")
assert get_config_dir() == "/var/tmp"
else:
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.delenv("BORG_CONFIG_DIR", raising=False)
assert get_config_dir() == os.path.join(home_dir, ".config", "borg")
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_config_dir() == os.path.join("/var/tmp/.config", "borg")
monkeypatch.setenv("BORG_CONFIG_DIR", "/var/tmp")
assert get_config_dir() == "/var/tmp"


def test_get_config_dir_compat(monkeypatch):
"""test that it works the same for legacy and for non-legacy implementation"""
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
if not is_darwin and not is_win32:
monkeypatch.delenv("BORG_CONFIG_DIR", raising=False)
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
# fails on macOS: assert '/Users/tw/Library/Application Support/borg' == '/Users/tw/.config/borg'
# fails on win32 MSYS2 (but we do not need legacy compat there).
assert get_config_dir(legacy=False) == get_config_dir(legacy=True)
if not is_darwin and not is_win32:
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config1")
# fails on macOS: assert '/Users/tw/Library/Application Support/borg' == '/var/tmp/.config1/borg'
# fails on win32 MSYS2 (but we do not need legacy compat there).
assert get_config_dir(legacy=False) == get_config_dir(legacy=True)
monkeypatch.setenv("BORG_CONFIG_DIR", "/var/tmp/.config2")
assert get_config_dir(legacy=False) == get_config_dir(legacy=True)
ThomasWaldmann marked this conversation as resolved.
Show resolved Hide resolved


def test_get_cache_dir(monkeypatch):
"""test that get_cache_dir respects environment"""
monkeypatch.delenv("BORG_CACHE_DIR", raising=False)
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
assert get_cache_dir() == os.path.join(os.path.expanduser("~"), ".cache", "borg")
monkeypatch.setenv("XDG_CACHE_HOME", "/var/tmp/.cache")
assert get_cache_dir() == os.path.join("/var/tmp/.cache", "borg")
monkeypatch.setenv("BORG_CACHE_DIR", "/var/tmp")
assert get_cache_dir() == "/var/tmp"
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
home_dir = os.path.expanduser("~")
if is_win32:
monkeypatch.delenv("BORG_CACHE_DIR", raising=False)
assert get_cache_dir() == os.path.join(home_dir, "AppData", "Local", "borg", "borg", "Cache")
monkeypatch.setenv("BORG_CACHE_DIR", home_dir)
assert get_cache_dir() == home_dir
elif is_darwin:
monkeypatch.delenv("BORG_CACHE_DIR", raising=False)
assert get_cache_dir() == os.path.join(home_dir, "Library", "Caches", "borg")
monkeypatch.setenv("BORG_CACHE_DIR", "/var/tmp")
assert get_cache_dir() == "/var/tmp"
else:
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
monkeypatch.delenv("BORG_CACHE_DIR", raising=False)
assert get_cache_dir() == os.path.join(home_dir, ".cache", "borg")
monkeypatch.setenv("XDG_CACHE_HOME", "/var/tmp/.cache")
assert get_cache_dir() == os.path.join("/var/tmp/.cache", "borg")
monkeypatch.setenv("BORG_CACHE_DIR", "/var/tmp")
assert get_cache_dir() == "/var/tmp"


def test_get_keys_dir(monkeypatch):
"""test that get_keys_dir respects environment"""
monkeypatch.delenv("BORG_KEYS_DIR", raising=False)
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
assert get_keys_dir() == os.path.join(os.path.expanduser("~"), ".config", "borg", "keys")
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_keys_dir() == os.path.join("/var/tmp/.config", "borg", "keys")
monkeypatch.setenv("BORG_KEYS_DIR", "/var/tmp")
assert get_keys_dir() == "/var/tmp"
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
home_dir = os.path.expanduser("~")
if is_win32:
monkeypatch.delenv("BORG_KEYS_DIR", raising=False)
assert get_keys_dir() == os.path.join(home_dir, "AppData", "Local", "borg", "borg", "keys")
monkeypatch.setenv("BORG_KEYS_DIR", home_dir)
assert get_keys_dir() == home_dir
elif is_darwin:
monkeypatch.delenv("BORG_KEYS_DIR", raising=False)
assert get_keys_dir() == os.path.join(home_dir, "Library", "Application Support", "borg", "keys")
monkeypatch.setenv("BORG_KEYS_DIR", "/var/tmp")
assert get_keys_dir() == "/var/tmp"
else:
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.delenv("BORG_KEYS_DIR", raising=False)
assert get_keys_dir() == os.path.join(home_dir, ".config", "borg", "keys")
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_keys_dir() == os.path.join("/var/tmp/.config", "borg", "keys")
monkeypatch.setenv("BORG_KEYS_DIR", "/var/tmp")
assert get_keys_dir() == "/var/tmp"


def test_get_security_dir(monkeypatch):
"""test that get_security_dir respects environment"""
monkeypatch.delenv("BORG_SECURITY_DIR", raising=False)
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
assert get_security_dir() == os.path.join(os.path.expanduser("~"), ".config", "borg", "security")
assert get_security_dir(repository_id="1234") == os.path.join(
os.path.expanduser("~"), ".config", "borg", "security", "1234"
)
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_security_dir() == os.path.join("/var/tmp/.config", "borg", "security")
monkeypatch.setenv("BORG_SECURITY_DIR", "/var/tmp")
assert get_security_dir() == "/var/tmp"
monkeypatch.delenv("BORG_BASE_DIR", raising=False)
home_dir = os.path.expanduser("~")
if is_win32:
monkeypatch.delenv("BORG_SECURITY_DIR", raising=False)
assert get_security_dir() == os.path.join(home_dir, "AppData", "Local", "borg", "borg", "security")
assert get_security_dir(repository_id="1234") == os.path.join(
home_dir, "AppData", "Local", "borg", "borg", "security", "1234"
)
monkeypatch.setenv("BORG_SECURITY_DIR", home_dir)
assert get_security_dir() == home_dir
elif is_darwin:
monkeypatch.delenv("BORG_SECURITY_DIR", raising=False)
assert get_security_dir() == os.path.join(home_dir, "Library", "Application Support", "borg", "security")
assert get_security_dir(repository_id="1234") == os.path.join(
home_dir, "Library", "Application Support", "borg", "security", "1234"
)
monkeypatch.setenv("BORG_SECURITY_DIR", "/var/tmp")
assert get_security_dir() == "/var/tmp"
else:
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.delenv("BORG_SECURITY_DIR", raising=False)
assert get_security_dir() == os.path.join(home_dir, ".config", "borg", "security")
assert get_security_dir(repository_id="1234") == os.path.join(home_dir, ".config", "borg", "security", "1234")
monkeypatch.setenv("XDG_CONFIG_HOME", "/var/tmp/.config")
assert get_security_dir() == os.path.join("/var/tmp/.config", "borg", "security")
monkeypatch.setenv("BORG_SECURITY_DIR", "/var/tmp")
assert get_security_dir() == "/var/tmp"


def test_file_size():
Expand Down