diff --git a/NEWS b/NEWS index f3ead6c1d0..89dcf7526d 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ ver 0.25 (not yet released) * player - support replay gain parameter in stream URI - preallocate physical RAM for audio buffer when playback starts +* configuration + - support $XDG_DATA_HOME, $XDG_STATE_HOME * switch to C++23 * require Meson 1.2 diff --git a/doc/mpd.conf.5.rst b/doc/mpd.conf.5.rst index 3bdb8c3207..5508163674 100644 --- a/doc/mpd.conf.5.rst +++ b/doc/mpd.conf.5.rst @@ -33,7 +33,9 @@ supported: - ``$XDG_CONFIG_HOME`` - ``$XDG_MUSIC_DIR`` - ``$XDG_CACHE_HOME`` +- ``$XDG_DATA_HOME`` - ``$XDG_RUNTIME_DIR`` +- ``$XDG_STATE_HOME`` Example: diff --git a/doc/mpdconf.example b/doc/mpdconf.example index dd16a74d99..cd9538eb55 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -55,7 +55,7 @@ # it was brought down. This setting is disabled by default and the server # state will be reset on server start up. # -#state_file "$XDG_RUNTIME_DIR/mpd/state" +#state_file "$XDG_STATE_HOME/mpd/state" #state_file "~/.mpd/state" # # The location of the sticker database. This is a database which diff --git a/src/config/Path.cxx b/src/config/Path.cxx index 423390f2ab..4796b2f015 100644 --- a/src/config/Path.cxx +++ b/src/config/Path.cxx @@ -81,10 +81,14 @@ GetVariable(std::string_view name) return GetUserConfigDir(); else if (name == "XDG_MUSIC_DIR"sv) return GetUserMusicDir(); + else if (name == "XDG_DATA_HOME"sv) + return GetUserDataDir(); else if (name == "XDG_CACHE_HOME"sv) return GetUserCacheDir(); else if (name == "XDG_RUNTIME_DIR"sv) return GetUserRuntimeDir(); + else if (name == "XDG_STATE_HOME"sv) + return GetUserStateDir(); else throw FmtRuntimeError("Unknown variable: {:?}", name); } diff --git a/src/fs/glue/StandardDirectory.cxx b/src/fs/glue/StandardDirectory.cxx index 4b8e5145a2..83a5c90239 100644 --- a/src/fs/glue/StandardDirectory.cxx +++ b/src/fs/glue/StandardDirectory.cxx @@ -345,6 +345,39 @@ GetAppCacheDir() noexcept #endif } +AllocatedPath +GetUserDataDir() noexcept +{ +#ifdef USE_XDG + // Check for $XDG_DATA_HOME + if (const auto path = GetExistingEnvDirectory("XDG_DATA_HOME"); + path != nullptr) + return AllocatedPath{path}; + + // Check for $HOME/.local/share + if (const auto home = GetHomeDir(); !home.IsNull()) + if (auto fallback = home / Path::FromFS(".local/share"); + IsValidDir(fallback)) + return fallback; + +#endif + return nullptr; +} + +AllocatedPath +GetAppDataDir() noexcept +{ +#if defined(USE_XDG) + if (const auto user_dir = GetUserDataDir(); !user_dir.IsNull()) { + auto dir = user_dir / app_filename; + CreateDirectoryNoThrow(dir); + return dir; + } + +#endif + return nullptr; +} + AllocatedPath GetUserRuntimeDir() noexcept { @@ -386,6 +419,47 @@ GetAppRuntimeDir() noexcept return nullptr; } +AllocatedPath +GetUserStateDir() noexcept +{ +#ifdef USE_XDG + // Check for $XDG_STATE_HOME + if (const auto path = GetExistingEnvDirectory("XDG_STATE_HOME"); + path != nullptr) + return AllocatedPath{path}; + + // Check for $HOME/.local/state + if (const auto home = GetHomeDir(); !home.IsNull()) + if (auto fallback = home / Path::FromFS(".local/state"); + IsValidDir(fallback)) + return fallback; +#endif + + return nullptr; +} + +AllocatedPath +GetAppStateDir() noexcept +{ +#if defined(__linux__) && !defined(ANDROID) + /* systemd specific; see systemd.exec(5) */ + if (const char *state_directory = getenv("STATE_DIRECTORY")) + if (auto dir = Split(std::string_view{state_directory}, ':').first; + !dir.empty()) + return AllocatedPath::FromFS(dir); +#endif + +#if defined(USE_XDG) + if (const auto user_dir = GetUserStateDir(); !user_dir.IsNull()) { + auto dir = user_dir / app_filename; + CreateDirectoryNoThrow(dir); + return dir; + } +#endif + + return nullptr; +} + #ifdef _WIN32 AllocatedPath diff --git a/src/fs/glue/StandardDirectory.hxx b/src/fs/glue/StandardDirectory.hxx index df154efd51..fade03e184 100644 --- a/src/fs/glue/StandardDirectory.hxx +++ b/src/fs/glue/StandardDirectory.hxx @@ -26,6 +26,20 @@ GetUserMusicDir() noexcept; AllocatedPath GetUserCacheDir() noexcept; +/** + * Obtains data directory for this application. + */ +[[gnu::const]] +AllocatedPath +GetAppDataDir() noexcept; + +/** + * Obtains data directory for the current user. + */ +[[gnu::const]] +AllocatedPath +GetUserDataDir() noexcept; + /** * Obtains cache directory for this application. */ @@ -47,6 +61,20 @@ GetUserRuntimeDir() noexcept; AllocatedPath GetAppRuntimeDir() noexcept; +/** + * Obtains the state directory for the current user. + */ +[[gnu::const]] +AllocatedPath +GetUserStateDir() noexcept; + +/** + * Obtains the state directory for this application. + */ +[[gnu::const]] +AllocatedPath +GetAppStateDir() noexcept; + #ifdef _WIN32 /** diff --git a/test/fs/TestParsePath.cxx b/test/fs/TestParsePath.cxx index 294b3e94aa..d6329c7c2b 100644 --- a/test/fs/TestParsePath.cxx +++ b/test/fs/TestParsePath.cxx @@ -68,6 +68,26 @@ GetAppCacheDir() noexcept #endif } +AllocatedPath +GetUserDataDir() noexcept +{ +#ifdef _WIN32 + return nullptr; +#else + return AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/share")); +#endif +} + +AllocatedPath +GetAppDataDir() noexcept +{ +#ifdef _WIN32 + return nullptr; +#else + return GetUserDataDir() / AllocatedPath::FromFS(PATH_LITERAL("mpd")); +#endif +} + AllocatedPath GetUserRuntimeDir() noexcept { @@ -88,6 +108,26 @@ GetAppRuntimeDir() noexcept #endif } +AllocatedPath +GetUserStateDir() noexcept +{ +#ifdef _WIN32 + return nullptr; +#else + return AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/state")); +#endif +} + +AllocatedPath +GetAppStateDir() noexcept +{ +#ifdef _WIN32 + return nullptr; +#else + return GetUserStateDir() / AllocatedPath::FromFS(PATH_LITERAL("mpd")); +#endif +} + const char * ConfigData::GetString([[maybe_unused]] ConfigOption option, const char *default_value) const noexcept @@ -136,7 +176,11 @@ TEST(ParsePath, XDG) EXPECT_EQ(ParsePath("$XDG_CONFIG_HOME/abc"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.config/abc"))); EXPECT_EQ(ParsePath("$XDG_MUSIC_DIR"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/Music"))); EXPECT_EQ(ParsePath("$XDG_CACHE_HOME"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.cache"))); + EXPECT_EQ(ParsePath("$XDG_DATA_HOME"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/share"))); + EXPECT_EQ(ParsePath("$XDG_DATA_HOME/mpd"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/share/mpd"))); EXPECT_EQ(ParsePath("$XDG_RUNTIME_DIR/mpd"), AllocatedPath::FromFS(PATH_LITERAL("/run/user/foo/mpd"))); + EXPECT_EQ(ParsePath("$XDG_STATE_HOME"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/state"))); + EXPECT_EQ(ParsePath("$XDG_STATE_HOME/mpd"), AllocatedPath::FromFS(PATH_LITERAL("/home/foo/.local/state/mpd"))); } #endif