Skip to content

Commit

Permalink
platformdirs: introduce user_videos_dir() (#169)
Browse files Browse the repository at this point in the history
Introduces the means to acquire the user's video directory path.

Feature suggested in #141.
  • Loading branch information
kemzeb authored Apr 26, 2023
1 parent 58be11f commit a889e94
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 49 deletions.
16 changes: 16 additions & 0 deletions src/platformdirs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ def user_pictures_dir() -> str:
return PlatformDirs().user_pictures_dir


def user_videos_dir() -> str:
"""
:returns: videos directory tied to the user
"""
return PlatformDirs().user_videos_dir


def user_runtime_dir(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
Expand Down Expand Up @@ -480,6 +487,13 @@ def user_pictures_path() -> Path:
return PlatformDirs().user_pictures_path


def user_videos_path() -> Path:
"""
:returns: videos path tied to the user
"""
return PlatformDirs().user_videos_path


def user_runtime_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
Expand Down Expand Up @@ -517,6 +531,7 @@ def user_runtime_path(
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand All @@ -528,6 +543,7 @@ def user_runtime_path(
"user_log_path",
"user_documents_path",
"user_pictures_path",
"user_videos_path",
"user_runtime_path",
"site_data_path",
"site_config_path",
Expand Down
1 change: 1 addition & 0 deletions src/platformdirs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand Down
23 changes: 23 additions & 0 deletions src/platformdirs/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ def user_pictures_dir(self) -> str:
"""
return _android_pictures_folder()

@property
def user_videos_dir(self) -> str:
"""
:return: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``
"""
return _android_videos_folder()

@property
def user_runtime_dir(self) -> str:
"""
Expand Down Expand Up @@ -144,6 +151,22 @@ def _android_pictures_folder() -> str:
return pictures_dir


@lru_cache(maxsize=1)
def _android_videos_folder() -> str:
""":return: videos folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass

Context = autoclass("android.content.Context") # noqa: N806
Environment = autoclass("android.os.Environment") # noqa: N806
videos_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath()
except Exception:
videos_dir = "/storage/emulated/0/DCIM/Camera"

return videos_dir


__all__ = [
"Android",
]
10 changes: 10 additions & 0 deletions src/platformdirs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ def user_documents_dir(self) -> str:
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user"""

@property
@abstractmethod
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user"""

@property
@abstractmethod
def user_runtime_dir(self) -> str:
Expand Down Expand Up @@ -183,6 +188,11 @@ def user_pictures_path(self) -> Path:
""":return: pictures path tied to the user"""
return Path(self.user_pictures_dir)

@property
def user_videos_path(self) -> Path:
""":return: videos path tied to the user"""
return Path(self.user_videos_dir)

@property
def user_runtime_path(self) -> Path:
""":return: runtime path tied to the user"""
Expand Down
5 changes: 5 additions & 0 deletions src/platformdirs/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
return os.path.expanduser("~/Pictures")

@property
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user, e.g. ``~/Movies``"""
return os.path.expanduser("~/Movies")

@property
def user_runtime_dir(self) -> str:
""":return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
Expand Down
7 changes: 7 additions & 0 deletions src/platformdirs/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ def user_pictures_dir(self) -> str:
"""
return _get_user_media_dir("XDG_PICTURES_DIR", "~/Pictures")

@property
def user_videos_dir(self) -> str:
"""
:return: videos directory tied to the user, e.g. ``~/Videos``
"""
return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")

@property
def user_runtime_dir(self) -> str:
"""
Expand Down
12 changes: 12 additions & 0 deletions src/platformdirs/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ def user_pictures_dir(self) -> str:
"""
return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))

@property
def user_videos_dir(self) -> str:
"""
:return: videos directory tied to the user e.g. ``%USERPROFILE%\\Videos``
"""
return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))

@property
def user_runtime_dir(self) -> str:
"""
Expand All @@ -126,6 +133,9 @@ def get_win_folder_from_env_vars(csidl_name: str) -> str:
if csidl_name == "CSIDL_MYPICTURES": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")

if csidl_name == "CSIDL_MYVIDEO": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")

env_var_name = {
"CSIDL_APPDATA": "APPDATA",
"CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
Expand All @@ -152,6 +162,7 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
"CSIDL_LOCAL_APPDATA": "Local AppData",
"CSIDL_PERSONAL": "Personal",
"CSIDL_MYPICTURES": "My Pictures",
"CSIDL_MYVIDEO": "My Video",
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand All @@ -172,6 +183,7 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
"CSIDL_LOCAL_APPDATA": 28,
"CSIDL_PERSONAL": 5,
"CSIDL_MYPICTURES": 39,
"CSIDL_MYVIDEO": 14,
}.get(csidl_name)
if csidl_const is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand Down
1 change: 1 addition & 0 deletions tests/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No
"user_log_dir": f"/data/data/com.example/cache{suffix}{'' if params.get('opinion', True) is False else '/log'}",
"user_documents_dir": "/storage/emulated/0/Documents",
"user_pictures_dir": "/storage/emulated/0/Pictures",
"user_videos_dir": "/storage/emulated/0/DCIM/Camera",
"user_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else '/tmp'}",
}
expected = expected_map[func]
Expand Down
1 change: 1 addition & 0 deletions tests/test_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_macos(params: dict[str, Any], func: str) -> None:
"user_log_dir": f"{home}/Library/Logs{suffix}",
"user_documents_dir": f"{home}/Documents",
"user_pictures_dir": f"{home}/Pictures",
"user_videos_dir": f"{home}/Movies",
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
}
expected = expected_map[func]
Expand Down
81 changes: 32 additions & 49 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,72 +13,55 @@
from platformdirs.unix import Unix


def test_user_documents_dir(mocker: MockerFixture) -> None:
example_path = "/home/example/ExampleDocumentsFolder"
@pytest.mark.parametrize("prop", ["user_documents_dir", "user_pictures_dir", "user_videos_dir"])
def test_user_media_dir(mocker: MockerFixture, prop: str) -> None:
example_path = "/home/example/ExampleMediaFolder"
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = example_path
assert Unix().user_documents_dir == example_path


def test_user_documents_dir_env_var(mocker: MockerFixture) -> None:
# Mock documents dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

example_path = "/home/example/ExampleDocumentsFolder"
mocker.patch.dict(os.environ, {"XDG_DOCUMENTS_DIR": example_path})

assert Unix().user_documents_dir == example_path


def test_user_documents_dir_default(mocker: MockerFixture) -> None:
# Mock documents dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

# Mock no XDG_DOCUMENTS_DIR env variable being set
mocker.patch.dict(os.environ, {"XDG_DOCUMENTS_DIR": ""})

# Mock home directory
mocker.patch.dict(os.environ, {"HOME": "/home/example"})
# Mock home directory for running the test on Windows
mocker.patch.dict(os.environ, {"USERPROFILE": "/home/example"})

assert Unix().user_documents_dir == "/home/example/Documents"


def test_user_pictures_dir(mocker: MockerFixture) -> None:
example_path = "/home/example/ExamplePicturesFolder"
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = example_path
assert Unix().user_pictures_dir == example_path


def test_user_pictures_dir_env_var(mocker: MockerFixture) -> None:
# Mock pictures dir not being in user-dirs.dirs file
assert getattr(Unix(), prop) == example_path


@pytest.mark.parametrize(
"env_var,prop",
[
pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", id="user_documents_dir"),
pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", id="user_pictures_dir"),
pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", id="user_videos_dir"),
],
)
def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) -> None:
# Mock media dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

example_path = "/home/example/ExamplePicturesFolder"
mocker.patch.dict(os.environ, {"XDG_PICTURES_DIR": example_path})
example_path = "/home/example/ExampleMediaFolder"
mocker.patch.dict(os.environ, {env_var: example_path})

assert Unix().user_pictures_dir == example_path
assert getattr(Unix(), prop) == example_path


def test_user_pictures_dir_default(mocker: MockerFixture) -> None:
# Mock pictures dir not being in user-dirs.dirs file
@pytest.mark.parametrize(
"env_var,prop,default_abs_path",
[
pytest.param("XDG_DOCUMENTS_DIR", "user_documents_dir", "/home/example/Documents", id="user_documents_dir"),
pytest.param("XDG_PICTURES_DIR", "user_pictures_dir", "/home/example/Pictures", id="user_pictures_dir"),
pytest.param("XDG_VIDEOS_DIR", "user_videos_dir", "/home/example/Videos", id="user_videos_dir"),
],
)
def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, default_abs_path: str) -> None:
# Mock media dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

# Mock no XDG_PICTURES_DIR env variable being set
mocker.patch.dict(os.environ, {"XDG_PICTURES_DIR": ""})
# Mock no XDG env variable being set
mocker.patch.dict(os.environ, {env_var: ""})

# Mock home directory
mocker.patch.dict(os.environ, {"HOME": "/home/example"})
# Mock home directory for running the test on Windows
mocker.patch.dict(os.environ, {"USERPROFILE": "/home/example"})

assert Unix().user_pictures_dir == "/home/example/Pictures"
assert getattr(Unix(), prop) == default_abs_path


class XDGVariable(typing.NamedTuple):
Expand Down

0 comments on commit a889e94

Please sign in to comment.