Skip to content

Commit

Permalink
platformdirs: introduce user_music_dir() (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
kemzeb authored Apr 27, 2023
1 parent 37d7687 commit 0a9774e
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 9 deletions.
16 changes: 16 additions & 0 deletions src/platformdirs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ def user_videos_dir() -> str:
return PlatformDirs().user_videos_dir


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


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


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


def user_runtime_path(
appname: str | None = None,
appauthor: str | None | Literal[False] = None,
Expand Down Expand Up @@ -532,6 +546,7 @@ def user_runtime_path(
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand All @@ -544,6 +559,7 @@ def user_runtime_path(
"user_documents_path",
"user_pictures_path",
"user_videos_path",
"user_music_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 @@ -11,6 +11,7 @@
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_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 @@ -86,6 +86,13 @@ def user_videos_dir(self) -> str:
"""
return _android_videos_folder()

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user e.g. ``/storage/emulated/0/Music``
"""
return _android_music_folder()

@property
def user_runtime_dir(self) -> str:
"""
Expand Down Expand Up @@ -167,6 +174,22 @@ def _android_videos_folder() -> str:
return videos_dir


@lru_cache(maxsize=1)
def _android_music_folder() -> str:
""":return: music 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
music_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath()
except Exception:
music_dir = "/storage/emulated/0/Music"

return music_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 @@ -133,6 +133,11 @@ def user_pictures_dir(self) -> str:
def user_videos_dir(self) -> str:
""":return: videos directory tied to the user"""

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

@property
@abstractmethod
def user_runtime_dir(self) -> str:
Expand Down Expand Up @@ -193,6 +198,11 @@ def user_videos_path(self) -> Path:
""":return: videos path tied to the user"""
return Path(self.user_videos_dir)

@property
def user_music_path(self) -> Path:
""":return: music path tied to the user"""
return Path(self.user_music_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 @@ -69,6 +69,11 @@ def user_videos_dir(self) -> str:
""":return: videos directory tied to the user, e.g. ``~/Movies``"""
return os.path.expanduser("~/Movies")

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

@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 @@ -143,6 +143,13 @@ def user_videos_dir(self) -> str:
"""
return _get_user_media_dir("XDG_VIDEOS_DIR", "~/Videos")

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user, e.g. ``~/Music``
"""
return _get_user_media_dir("XDG_MUSIC_DIR", "~/Music")

@property
def user_runtime_dir(self) -> str:
"""
Expand Down
36 changes: 28 additions & 8 deletions src/platformdirs/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def user_videos_dir(self) -> str:
"""
return os.path.normpath(get_win_folder("CSIDL_MYVIDEO"))

@property
def user_music_dir(self) -> str:
"""
:return: music directory tied to the user e.g. ``%USERPROFILE%\\Music``
"""
return os.path.normpath(get_win_folder("CSIDL_MYMUSIC"))

@property
def user_runtime_dir(self) -> str:
"""
Expand All @@ -127,14 +134,9 @@ def user_runtime_dir(self) -> str:

def get_win_folder_from_env_vars(csidl_name: str) -> str:
"""Get folder from environment variables."""
if csidl_name == "CSIDL_PERSONAL": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")

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")
result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
if result is not None:
return result

env_var_name = {
"CSIDL_APPDATA": "APPDATA",
Expand All @@ -149,6 +151,22 @@ def get_win_folder_from_env_vars(csidl_name: str) -> str:
return result


def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:
"""Get folder for a CSIDL name that does not exist as an environment variable."""
if csidl_name == "CSIDL_PERSONAL":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")

if csidl_name == "CSIDL_MYPICTURES":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")

if csidl_name == "CSIDL_MYVIDEO":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")

if csidl_name == "CSIDL_MYMUSIC":
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music")
return None


def get_win_folder_from_registry(csidl_name: str) -> str:
"""Get folder from the registry.
Expand All @@ -163,6 +181,7 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
"CSIDL_PERSONAL": "Personal",
"CSIDL_MYPICTURES": "My Pictures",
"CSIDL_MYVIDEO": "My Video",
"CSIDL_MYMUSIC": "My Music",
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand All @@ -184,6 +203,7 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
"CSIDL_PERSONAL": 5,
"CSIDL_MYPICTURES": 39,
"CSIDL_MYVIDEO": 14,
"CSIDL_MYMUSIC": 13,
}.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 @@ -14,6 +14,7 @@
"user_documents_dir",
"user_pictures_dir",
"user_videos_dir",
"user_music_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 @@ -54,6 +54,7 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No
"user_documents_dir": "/storage/emulated/0/Documents",
"user_pictures_dir": "/storage/emulated/0/Pictures",
"user_videos_dir": "/storage/emulated/0/DCIM/Camera",
"user_music_dir": "/storage/emulated/0/Music",
"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 @@ -35,6 +35,7 @@ def test_macos(params: dict[str, Any], func: str) -> None:
"user_documents_dir": f"{home}/Documents",
"user_pictures_dir": f"{home}/Pictures",
"user_videos_dir": f"{home}/Movies",
"user_music_dir": f"{home}/Music",
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
}
expected = expected_map[func]
Expand Down
4 changes: 3 additions & 1 deletion tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from platformdirs.unix import Unix


@pytest.mark.parametrize("prop", ["user_documents_dir", "user_pictures_dir", "user_videos_dir"])
@pytest.mark.parametrize("prop", ["user_documents_dir", "user_pictures_dir", "user_videos_dir", "user_music_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")
Expand All @@ -27,6 +27,7 @@ def test_user_media_dir(mocker: MockerFixture, prop: str) -> None:
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"),
pytest.param("XDG_MUSIC_DIR", "user_music_dir", id="user_music_dir"),
],
)
def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str) -> None:
Expand All @@ -46,6 +47,7 @@ def test_user_media_dir_env_var(mocker: MockerFixture, env_var: str, prop: str)
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"),
pytest.param("XDG_MUSIC_DIR", "user_music_dir", "/home/example/Music", id="user_music_dir"),
],
)
def test_user_media_dir_default(mocker: MockerFixture, env_var: str, prop: str, default_abs_path: str) -> None:
Expand Down

0 comments on commit 0a9774e

Please sign in to comment.