From a889e943e31203fff3b7fbb6180918088df8abca Mon Sep 17 00:00:00 2001 From: Kemal Zebari <60799661+kemzeb@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:52:54 -0700 Subject: [PATCH] platformdirs: introduce `user_videos_dir()` (#169) Introduces the means to acquire the user's video directory path. Feature suggested in #141. --- src/platformdirs/__init__.py | 16 +++++++ src/platformdirs/__main__.py | 1 + src/platformdirs/android.py | 23 ++++++++++ src/platformdirs/api.py | 10 +++++ src/platformdirs/macos.py | 5 +++ src/platformdirs/unix.py | 7 ++++ src/platformdirs/windows.py | 12 ++++++ tests/conftest.py | 1 + tests/test_android.py | 1 + tests/test_macos.py | 1 + tests/test_unix.py | 81 ++++++++++++++---------------------- 11 files changed, 109 insertions(+), 49 deletions(-) diff --git a/src/platformdirs/__init__.py b/src/platformdirs/__init__.py index 0bdaa462..2b61b5d2 100644 --- a/src/platformdirs/__init__.py +++ b/src/platformdirs/__init__.py @@ -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, @@ -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, @@ -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", @@ -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", diff --git a/src/platformdirs/__main__.py b/src/platformdirs/__main__.py index 72a91bbd..d19fee2d 100644 --- a/src/platformdirs/__main__.py +++ b/src/platformdirs/__main__.py @@ -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", diff --git a/src/platformdirs/android.py b/src/platformdirs/android.py index 50395e8a..721a8165 100644 --- a/src/platformdirs/android.py +++ b/src/platformdirs/android.py @@ -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: """ @@ -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", ] diff --git a/src/platformdirs/api.py b/src/platformdirs/api.py index 3bab4686..cf1d4e7f 100644 --- a/src/platformdirs/api.py +++ b/src/platformdirs/api.py @@ -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: @@ -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""" diff --git a/src/platformdirs/macos.py b/src/platformdirs/macos.py index ac0dfb56..12cd9e63 100644 --- a/src/platformdirs/macos.py +++ b/src/platformdirs/macos.py @@ -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``""" diff --git a/src/platformdirs/unix.py b/src/platformdirs/unix.py index 6c7b353f..7358b84f 100644 --- a/src/platformdirs/unix.py +++ b/src/platformdirs/unix.py @@ -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: """ diff --git a/src/platformdirs/windows.py b/src/platformdirs/windows.py index e549ad69..835b336c 100644 --- a/src/platformdirs/windows.py +++ b/src/platformdirs/windows.py @@ -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: """ @@ -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", @@ -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}") @@ -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}") diff --git a/tests/conftest.py b/tests/conftest.py index ebaa0b11..8ec9c584 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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", diff --git a/tests/test_android.py b/tests/test_android.py index 558e5f86..60a52265 100644 --- a/tests/test_android.py +++ b/tests/test_android.py @@ -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] diff --git a/tests/test_macos.py b/tests/test_macos.py index 2d31f11c..0d9a3124 100644 --- a/tests/test_macos.py +++ b/tests/test_macos.py @@ -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] diff --git a/tests/test_unix.py b/tests/test_unix.py index da82513c..cbf87bcb 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -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):