diff --git a/CHANGELOG.md b/CHANGELOG.md index f88d8ee4..f152f5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,19 @@ ## Unreleased +### Update notes + +The project uses now a library to manage user directories on the different operating systems, the location was modified for Windows: + +```cmd +# cmd +mkdir %APPDATA%\DakaraProject +move %APPDATA%\Dakara %APPDATA%\DakaraProject\dakara +# powershell +mkdir $env:APPDATA\DakaraProject +mv $env:APPDATA\Dakara $env:APPDATA\DakaraProject\dakara +``` + ### Added - Fonts are automatically installed on Windows. diff --git a/README.md b/README.md index f0e3782d..82cecd07 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ It is strongly recommended to use the Dakara player within a virtual environment ### Install +Please ensure you have a recent enough version of `setuptools`: + +```sh +pip install --upgrade "setuptools>=46.4.0" +``` + Install the package with: ```sh @@ -73,12 +79,12 @@ dakara-player create-config python -m dakara_player create-config ``` -and complete it with your values. The file is stored in your user space: `~/.config/dakara` on Linux or `$APPDATA\Dakara` on Windows. +and complete it with your values. The file is stored in your user space: `~/.config/dakara` on Linux, or `$APPDATA\DakaraProject\dakara` on Windows. ## Customization The different text screens used when the player is idle, or before a song, can be customized, both for the background and the text template. -The program looks for custom files at startup in the user directory: `~/.local/share/dakara/player` on Linux or `$APPDATA\Dakara\player` on Windows. +The program looks for custom files at startup in the user directory: `~/.local/share/dakara/player` on Linux or `$APPDATA\DakaraProject\dakara\player` on Windows. Backgrounds are located in the `backgrounds` subfolder, and text templates in the `templates` subfolder. File names can be modified in the config file, see `player.templates` and `player.backgrounds`. diff --git a/setup.cfg b/setup.cfg index 46fed299..8677ffe6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ package_dir = packages = find: # dependencies are pinned by interval install_requires = - dakarabase>=1.4.0,<1.5.0 + dakarabase>=1.4.2,<1.5.0 filetype>=1.0.10,<1.1.0 importlib-resources>=5.6.0,<5.7.0; python_version < '3.7' Jinja2>=3.1.1,<3.2.0 diff --git a/src/dakara_player/font.py b/src/dakara_player/font.py index 93fbd735..c3a2db95 100644 --- a/src/dakara_player/font.py +++ b/src/dakara_player/font.py @@ -2,7 +2,7 @@ import ctypes import logging -import sys +import platform from abc import ABC, abstractmethod from path import Path @@ -26,14 +26,16 @@ def get_font_loader_class(): Returns: FontLoader: Specialized version of the font loader class. """ - if "linux" in sys.platform: + system = platform.system() + + if system == "Linux": return FontLoaderLinux - if "win" in sys.platform: + if system == "Windows": return FontLoaderWindows raise NotImplementedError( - "This operating system ({}) is not currently supported".format(sys.platform) + "This operating system ({}) is not currently supported".format(system) ) @@ -136,14 +138,30 @@ def __init__(self, *args, **kwargs): # create list of fonts self.fonts_loaded = {} + def get_system_font_path_list(self): + """Retrieve the list of system fonts. + + Returns: + list of path.Path: List of font paths. + """ + return list(self.FONT_DIR_SYSTEM.walkfiles()) + + def get_user_font_path_list(self): + """Retrieve the list of user fonts. + + Returns: + list of path.Path: List of font paths. + """ + return list(self.FONT_DIR_USER.expanduser().walkfiles()) + def load(self): """Load the fonts.""" # ensure that the user font directory exists self.FONT_DIR_USER.expanduser().mkdir_p() # get system and user font files - system_font_path_list = list(self.FONT_DIR_SYSTEM.walkfiles()) - user_font_path_list = list(self.FONT_DIR_USER.expanduser().walkfiles()) + system_font_path_list = self.get_system_font_path_list() + user_font_path_list = self.get_user_font_path_list() # load fonts for font_file_path in self.get_font_path_iterator(): diff --git a/src/dakara_player/media_player/base.py b/src/dakara_player/media_player/base.py index 56218c0c..f8cacd0c 100644 --- a/src/dakara_player/media_player/base.py +++ b/src/dakara_player/media_player/base.py @@ -5,6 +5,7 @@ from functools import wraps from threading import Timer +from dakara_base.directory import directories from dakara_base.exceptions import DakaraError from dakara_base.safe_workers import Worker from path import Path @@ -12,7 +13,6 @@ from dakara_player.audio import get_audio_files from dakara_player.background import BackgroundLoader from dakara_player.text import TextGenerator -from dakara_player.user_resources import get_user_directory from dakara_player.version import __version__ TRANSITION_BG_NAME = "transition.png" @@ -125,7 +125,7 @@ def init_worker(self, config, tempdir, warn_long_exit=True): config_texts = config.get("templates") or {} self.text_generator = TextGenerator( package="dakara_player.resources.templates", - directory=get_user_directory().expand() / "templates", + directory=directories.user_data_dir / "player" / "templates", filenames={ "transition": config_texts.get( "transition_template_name", TRANSITION_TEXT_NAME @@ -139,7 +139,7 @@ def init_worker(self, config, tempdir, warn_long_exit=True): self.background_loader = BackgroundLoader( destination=tempdir, package="dakara_player.resources.backgrounds", - directory=get_user_directory().expand() / "backgrounds", + directory=directories.user_data_dir / "player" / "backgrounds", filenames={ "transition": config_backgrounds.get( "transition_background_name", TRANSITION_BG_NAME diff --git a/src/dakara_player/media_player/vlc.py b/src/dakara_player/media_player/vlc.py index c2f53f53..24aa2145 100644 --- a/src/dakara_player/media_player/vlc.py +++ b/src/dakara_player/media_player/vlc.py @@ -2,8 +2,8 @@ import json import logging +import platform import re -import sys from dakara_base.exceptions import DakaraError from dakara_base.safe_workers import safe @@ -726,18 +726,20 @@ def set_window(self, id): logger.debug("Using VLC default window") return - if "linux" in sys.platform: + system = platform.system() + + if system == "Linux": logger.debug("Associating X window to VLC") self.player.set_xwindow(id) return - if "win" in sys.platform: + if system == "Windows": logger.debug("Associating Win API window to VLC") self.player.set_hwnd(id) return raise NotImplementedError( - "This operating system ({}) is not currently supported".format(sys.platform) + "This operating system ({}) is not currently supported".format(system) ) diff --git a/src/dakara_player/user_resources.py b/src/dakara_player/user_resources.py index 4747a73b..3876919c 100644 --- a/src/dakara_player/user_resources.py +++ b/src/dakara_player/user_resources.py @@ -1,7 +1,6 @@ """Manage the user resource directory and files.""" import logging -import sys from distutils.util import strtobool try: @@ -10,29 +9,12 @@ except ImportError: from importlib_resources import path, contents +from dakara_base.directory import directories from path import Path logger = logging.getLogger(__name__) -def get_user_directory(): - """Get the user directory for resource files. - - Returns: - path.Path: Path to the user directory, must be expanded to be used. - - Raises: - NotImplementedError: If the current platform is not supported. - """ - if "linux" in sys.platform: - return Path("~") / ".local" / "share" / "dakara" / "player" - - if "win" in sys.platform: - return Path("$APPDATA") / "Dakara" / "player" - - raise NotImplementedError("Operating system not supported") - - def copy_resource(resource, destination, force): """Copy the content of one resource directory. @@ -75,7 +57,7 @@ def create_resource_files(force=False): force (bool): If the user directory already contains the resource directories and this flag is set, overwrite the directories. """ - user_directory = get_user_directory().expand() + user_directory = directories.user_data_dir user_directory.makedirs_p() for directory in ["backgrounds", "templates"]: diff --git a/tests/unit/test_font.py b/tests/unit/test_font.py index 88d63812..b64202be 100644 --- a/tests/unit/test_font.py +++ b/tests/unit/test_font.py @@ -1,4 +1,4 @@ -import sys +import platform from unittest import TestCase, skipUnless from unittest.mock import call, patch @@ -18,17 +18,17 @@ class GetFontLoaderClassTestCase(TestCase): def test(self): """Test to get the correct font loader class for the platform.""" # call for Linux - with patch("dakara_player.font.sys.platform", "linux"): + with patch("dakara_player.font.platform.system", return_value="Linux"): FontLoaderClass = get_font_loader_class() self.assertEqual(FontLoaderClass, FontLoaderLinux) # call for Windows - with patch("dakara_player.font.sys.platform", "win32"): + with patch("dakara_player.font.platform.system", return_value="Windows"): FontLoaderClass = get_font_loader_class() self.assertEqual(FontLoaderClass, FontLoaderWindows) # call for uniplemented OS - with patch("dakara_player.font.sys.platform", "other"): + with patch("dakara_player.font.platform.system", return_value="other"): with self.assertRaisesRegex( NotImplementedError, r"This operating system \(other\) is not currently supported", @@ -110,7 +110,7 @@ def test_get_font_path_iterator(self, mocked_get_font_name_list, mocked_path): self.assertListEqual(font_file_path_list, [self.font_path]) -@skipUnless(sys.platform.startswith("linux"), "Can be tested on Linux only") +@skipUnless(platform.system() == "Linux", "Can be tested on Linux only") class FontLoaderLinuxTestCase(FontLoaderTestCase): """Test the Linux font loader.""" diff --git a/tests/unit/test_media_player_vlc.py b/tests/unit/test_media_player_vlc.py index 031d538d..90f24cfe 100644 --- a/tests/unit/test_media_player_vlc.py +++ b/tests/unit/test_media_player_vlc.py @@ -5,7 +5,7 @@ from threading import Event from time import sleep from unittest import TestCase, skipIf -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, PropertyMock, patch try: import vlc @@ -13,6 +13,7 @@ except (ImportError, OSError): vlc = None +from dakara_base.directory import AppDirsPath from packaging.version import parse from path import Path @@ -1069,10 +1070,10 @@ def test_handle_paused(self, mocked_get_timing): # assert the call vlc_player.callbacks["paused"].assert_called_with(42, 25) - @patch("dakara_player.media_player.base.get_user_directory") - def test_custom_backgrounds(self, mocked_get_user_directory): + @patch.object(AppDirsPath, "user_data_dir", new_callable=PropertyMock) + def test_custom_backgrounds(self, mocked_user_data_dir): """Test to instanciate with custom backgrounds.""" - mocked_get_user_directory.return_value = Path("custom") + mocked_user_data_dir.return_value = Path("directory") # create object tempdir = Path("temp") @@ -1090,7 +1091,7 @@ def test_custom_backgrounds(self, mocked_get_user_directory): mocked_background_loader_class.assert_called_with( destination=tempdir, package="dakara_player.resources.backgrounds", - directory=Path("custom") / "backgrounds", + directory=Path("directory") / "player" / "backgrounds", filenames={ "transition": "custom_transition.png", "idle": "custom_idle.png", @@ -1169,8 +1170,8 @@ def test_set_window_none(self): ["DEBUG:dakara_player.media_player.vlc:Using VLC default window"], ) - @patch("dakara_player.media_player.vlc.sys.platform", "linux") - def test_set_window_linux(self): + @patch("dakara_player.media_player.vlc.platform.system", return_value="Linux") + def test_set_window_linux(self, mocked_system): """Test to use X window.""" with self.get_instance() as (vlc_player, _, _): with self.assertLogs("dakara_player.media_player.vlc", "DEBUG") as logger: @@ -1181,8 +1182,8 @@ def test_set_window_linux(self): ["DEBUG:dakara_player.media_player.vlc:Associating X window to VLC"], ) - @patch("dakara_player.media_player.vlc.sys.platform", "win32") - def test_set_window_windows(self): + @patch("dakara_player.media_player.vlc.platform.system", return_value="Windows") + def test_set_window_windows(self, mocked_system): """Test to use Win API window.""" with self.get_instance() as (vlc_player, _, _): with self.assertLogs("dakara_player.media_player.vlc", "DEBUG") as logger: @@ -1196,9 +1197,8 @@ def test_set_window_windows(self): ], ) - @patch("dakara_player.media_player.base.get_user_directory", autospec=True) - @patch("dakara_player.media_player.vlc.sys.platform", "other") - def test_set_window_other(self, mocked_get_user_directory): + @patch("dakara_player.media_player.vlc.platform.system", return_value="other") + def test_set_window_other(self, mocked_system): """Test to set window on unknown platform.""" with self.get_instance() as (vlc_player, _, _): with self.assertRaises(NotImplementedError): diff --git a/tests/unit/test_user_resources.py b/tests/unit/test_user_resources.py index 25853573..11c814f0 100644 --- a/tests/unit/test_user_resources.py +++ b/tests/unit/test_user_resources.py @@ -1,36 +1,12 @@ from unittest import TestCase -from unittest.mock import call, patch +from unittest.mock import PropertyMock, call, patch +from dakara_base.directory import AppDirsPath from path import Path from dakara_player import user_resources -class GetUserDirectoryTestCase(TestCase): - """Test the get_user_directory function.""" - - @patch("dakara_player.user_resources.sys.platform", "linux") - def test_get_linux(self): - """Test get user directory on Linux.""" - self.assertIn( - Path(".local") / "share" / "dakara" / "player", - user_resources.get_user_directory().expand(), - ) - - @patch("dakara_player.user_resources.sys.platform", "win32") - def test_get_windows(self): - """Test get user directory on Windows.""" - self.assertIn( - Path("Dakara") / "player", user_resources.get_user_directory().expand() - ) - - @patch("dakara_player.user_resources.sys.platform", "unknown") - def test_get_unknown(self): - """Test get user directory on unknown OS.""" - with self.assertRaises(NotImplementedError): - user_resources.get_user_directory().expand() - - class CopyResourceTestCase(TestCase): """Test the copy_resource function.""" @@ -202,17 +178,17 @@ def test_copy_existing_force( ) -@patch("dakara_player.user_resources.get_user_directory", autospec=True) +@patch.object(AppDirsPath, "user_data_dir", new_callable=PropertyMock) @patch("dakara_player.user_resources.copy_resource", autospec=True) class CreateResourceFilesTestCase(TestCase): """Test the create_resource_files function.""" @patch.object(Path, "makedirs_p", autospec=True) def test_create( - self, mocked_makedirs_p, mocked_copy_resource, mocked_get_user_directory + self, mocked_makedirs_p, mocked_copy_resource, mocked_user_data_dir ): """Test to create resource files.""" - mocked_get_user_directory.return_value = Path("directory") + mocked_user_data_dir.return_value = Path("directory") with self.assertLogs("dakara_player.user_resources", "DEBUG") as logger: user_resources.create_resource_files()