/* * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted. * * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * This module provides quick & easy access to the common directories * for each operating system. Currently, only POSIX (XDG Base Directory * Specification) and Windows (Known Folder) are supported. * OS X (Standard Directories) will be supported at a later date. * * The main goal of this module is to provide a minimal and simple API. * * ## Example * * --- * import std.stdio : writefln; * import std.path : buildPath; * * import mlib.directories : getProjectDirectories; * * void main(string[] args) * { * ProjectDirectories projectDirs = getProjectDirectories("org", "Example ORG", "My Program"); * auto config = readConfig(projectDirs.configDir); * * // ...rest of program * } * * auto readConfig(string path) * { * // ...some implementation * } * * --- * * * This module supports D version greater than or equal to 2.076.0. * * Authors: nemophila * Date: March 6, 2023 * Homepage: https://osdn.net/users/nemophila/pf/mlib * License: $(LINK2 https://osdn.net/users/nemophila/pf/mlib/scm/blobs/trunk/LICENSE, 0BSD) * Standards: * $(UL * $(LI $(LINK2 https://specifications.freedesktop.org/basedir-spec/0.8/, XDG Base Directory Specification 0.8)) * $(LI $(LINK2 https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb776911(v=vs.85), Known Folders)) * ) * Version: 0.2.0 * * History: * 0.X.X was the initial version (June 12, 2021) * * 0.1.0 adds support for runtime and state (February 18, 2023) * * 0.2.0 Re-wrote the API. Now supports Windows. Bump minimum D version to 2.076.0 (March 26, 2023) */ module mlib.directories; import std.file : DirEntry; /// /// Deprecated: Use the new `getBaseDirectories`, `getProjectDirectories`, /// and `getUserDirectories` functions. This will be removed /// in version 0.3.0. /// deprecated("Use new get{Base,Project,User}Directories functions (remove 0.3.0)") public enum Directory { home, data, config, state, // dataDirs, /* XDG */ // configDirs, /* XDG */ cache, runtime, } /// /// Provides paths of user-invisible standard directories, following the /// conventions of the operating system the library is running on. /// /// To compute the location of cache, config or data directories for /// individual projects or applications, use ProjectDirectories instead. /// /// ## Examples /// /// All examples on this page are computed with a user named /// $(I Elq). /// /// An example of `BaseDirectories#configDir`: /// /// $(UL /// $(LI $(B Posix): `/home/elq/.config`) /// $(LI $(B macOS): $(RED Currently not supported)) /// $(LI $(B Windows): `C:\Users\Elq\AppData\Roaming`) /// ) /// struct BaseDirectories { /// /// Returns the path to the user's home directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$HOME`) /// $(TD /home/elq) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Profile`) /// $(TD C:\Users\Elq) /// ) /// ) /// immutable string homeDir; /// /// Returns the path to the user's cache directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CACHE_HOME` (fallback: `$HOME/.cache`)) /// $(TD /home/elq/.cache) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_LocalAppData`) /// $(TD C:\Users\Elq\AppData\Local) /// ) /// ) /// immutable string cacheDir; /// /// Returns the path to the user's configuration directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CONFIG_HOME` (fallback: `$HOME/.config`)) /// $(TD /home/elq/.config) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData`) /// $(TD C:\Users\Elq\AppData\Roaming) /// ) /// ) /// immutable string configDir; /// /// Returns the path to the user's data directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DATA_HOME` (fallback: `$HOME/.local/share`)) /// $(TD /home/elq/.local/share) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData`) /// $(TD C:\Users\Elq\AppData\Roaming) /// ) /// ) /// immutable string dataDir; /// /// Returns the path to the user's local data directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DATA_HOME` (fallback: `$HOME/.local/share`)) /// $(TD /home/elq/.local/share) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_LocalAppData`) /// $(TD C:\Users\Elq\AppData\Local) /// ) /// ) /// immutable string dataLocalDir; /// /// Returns the path to the user's local executable directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$HOME/.local/bin`) /// $(TD /home/elq/.local/bin) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD N/A) /// $(TD `""`) /// ) /// ) /// immutable string executableDir; /// /// Returns the path to the user's preference directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CONFIG_HOME` (fallback: `$HOME/.config`)) /// $(TD /home/elq/.config) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData`) /// $(TD C:\Users\Elq\AppData\Roaming) /// ) /// ) /// immutable string preferenceDir; /// /// Returns the path to the user's runtime directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_RUNTIME_DIR`) /// $(TD /run/user/1000) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD N/A) /// $(TD `""`) /// ) /// ) /// immutable string runtimeDir; string toString() const @safe pure nothrow { version (OSX) { enum platform = "OSX"; } else version (Posix) { enum platform = "Posix"; } else version (Windows) { enum platform = "Windows"; } return "BaseDirectories(" ~ platform ~ "):\n" ~ " homeDir = '" ~ homeDir ~ "'\n" ~ " cacheDir = '" ~ cacheDir ~ "'\n" ~ " configDir = '" ~ configDir ~ "'\n" ~ " dataDir = '" ~ dataDir ~ "'\n" ~ " dataLocalDir = '" ~ dataLocalDir ~ "'\n" ~ " executableDir = '" ~ executableDir ~ "'\n" ~ " preferenceDir = '" ~ preferenceDir ~ "'\n" ~ " runtimeDir = '" ~ runtimeDir ~ "'\n"; } } /// /// Returns a new instance of `BaseDirectories`. /// /// The instance is an immutable snapshop of the state of the system at /// the time this function is called. Subsequent changes to the state /// of the system are not reflected in instances created prior to such /// a change. /// nothrow BaseDirectories getBaseDirectories() { // OS X first so it doesn't get mixed with Posix. version (OSX) { // Support will be added. static assert(false, "mlib.directories: Unsupported operating system."); } else version (Posix) { return BaseDirectories( posixHome(), xdgCache(), xdgConfig(), xdgData(), xdgData(), // from spec: // User-specific executable files may be stored in $HOME/.local/bin. buildPath(posixHome(), ".local", "bin"), xdgConfig(), xdgRuntime() ); } else version (Windows) { string dataDir = windowsRoamingData(); string localDataDir = windowsLocalData(); return BaseDirectories( windowsHome(), localDataDir, dataDir, dataDir, localDataDir, "", dataDir, "" ); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } /// unittest { import std.stdio : writeln; BaseDirectories baseDirs = getBaseDirectories(); writeln(baseDirs); } /// /// `ProjectDirectories ` computes the location of cache, config, or /// data directories for a specific application, which are derived from /// the standard directories and the name of the project/organisation. /// /// ## Examples /// /// All examples in this section are computed with a user named *Elq*, /// and a `ProjectDirectories` instance created with the following /// information: /// /// ```d /// ProjectDirectories projectDirs = getProjectDirectories("com", "Foo Corp", "Bar App"); /// ``` /// /// Example of `ProjectDirectories#configDir` value in different /// operating systems: /// /// $(UL /// $(LI $(B Posix): `/home/elq/.config/barapp`) /// $(LI $(B macOS): $(RED Platform not supported)) /// $(LI $(B Windows): `C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config`) /// ) /// struct ProjectDirectories { /// /// The path to the project's cache directory in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CACHE_HOME/` (fallback: `$HOME/.cache/`)) /// $(TD /home/elq/.cache/barapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_LocalAppData\\cache`) /// $(TD C:\Users\Elq\AppData\Local\Foo Corp\Bar App\cache ) /// ) /// ) /// immutable string cacheDir; /// /// The path to the project's configuration directory, in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CONFIG_HOME/` (fallback: `$HOME/.config//`)) /// $(TD /home/elq/.config/barapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData\\config`) /// $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config ) /// ) /// ) /// immutable string configDir; /// /// The path to the project's data directory, in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DATA_HOME/` (fallback: `$HOME/.local/share//`)) /// $(TD /home/elq/.local/share/barapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData\\data`) /// $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\data ) /// ) /// ) /// immutable string dataDir; /// /// The path to the project's local data directory, in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DATA_HOME/` (fallback: `$HOME/.local/share//`)) /// $(TD /home/elq/.local/share/barapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_LocalAppData\\data`) /// $(TD C:\Users\Elq\AppData\Local\Foo Corp\Bar App\data ) /// ) /// ) /// immutable string dataLocalDir; /// /// The path to the project's preference directory, in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_CONFIG_HOME/` (fallback: `$HOME/.config//`)) /// $(TD /home/elq/.config/barapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_RoamingAppData\\config`) /// $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config) /// ) /// ) /// immutable string preferenceDir; /// /// The path to the project's data directory, in which /// `` is the value of `ProjectDirectories#projectPath`. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_RUNTIME_DIR`) /// $(TD /run/user/1001/bareapp) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD N/A) /// $(TD `""`) /// ) /// ) /// immutable string runtimeDir; /// /// The project path fragment used to compute the project's /// cache/config/data directories. /// /// The value is derived from the arguments provided to the /// `getProjectDirectories()` function and is platform-dependent. /// immutable string projectPath; string toString() const @safe pure nothrow { version (OSX) { enum platform = "OSX"; } else version (Posix) { enum platform = "Posix"; } else version (Windows) { enum platform = "Windows"; } return "ProjectDirectories(" ~ platform ~ ")\n" ~ " projectPath = '" ~ projectPath ~ "'\n" ~ " cacheDir = '" ~ cacheDir ~ "'\n" ~ " configDir = '" ~ configDir ~ "'\n" ~ " dataDir = '" ~ dataDir ~ "'\n" ~ " dataLocalDir = '" ~ dataLocalDir ~ "'\n" ~ " preferenceDir = '" ~ preferenceDir ~ "'\n" ~ " runtimeDir = '" ~ runtimeDir ~ "'\n"; } } /// /// Return an instance of `ProjectDirectories` from values describing /// the project. /// /// Params: /// qualifier = The reverse domain name notation of the application, /// excluding the organisation or application name itself. $(BR) /// An example string can be passed if no qualifier should /// be used (only affects macOS). $(BR) /// Example values: `"com.example"`, `"org"`, `"co.uk"`, `""`. /// /// organisation = The name of the organisation that develops this application, /// or for which the application is developed.$(BR) /// An empty string can be passed if no organisation should be /// used (only affects macOS and Windows).$(BR) /// Example values: `"Foo Corp"`, `"Alice and Bob Inc"`, `""`. /// /// application = The name of the application itself.$(BR) /// Example values: `"Bar App"`, `"ExampleProgram"`, `"Unicorn-Programme"`. /// /// Returns: An instance of `ProjectDirectories`, whose directory field values are /// based on the `qualifier`, `organisation`, and `application` arguments. /// ProjectDirectories getProjectDirectories(string qualifier, string organisation, string application) nothrow in { assert(!empty(organisation) && !empty(application), "The organisation and the application arguments cannot both be empty"); } do { version (OSX) { // Support will be added. static assert(false, "mlib.directories: Unsupported operating system."); } else version (Posix) { import std.uni : isWhite, toLower; // Yes, we could call toLower(replace(string(application), " ", "-")) // but that could throw. This is nothrow. string subPath; bool reachedNonWhitespace = false; foreach(c; application) { if (' ' == c) { // Check it's not a newline or something silly. if (reachedNonWhitespace && false == isWhite(c)) { subPath ~= '-'; // We only want one '-', so make sure // to skip any succeeding spaces. reachedNonWhitespace = false; } } else { subPath ~= toLower(c); reachedNonWhitespace = true; } } string configDir = buildPath(xdgConfig(), subPath); string dataDir = buildPath(xdgData(), subPath); return ProjectDirectories( buildPath(xdgCache(), subPath), configDir, dataDir, dataDir, configDir, xdgRuntime(subPath), subPath ); } else version (Windows) { bool hasOrg = !empty(organisation); bool hasApp = !empty(application); string subPath; if (hasOrg) { subPath = organisation; if (hasApp) { subPath ~= "\\"; } } if (hasApp) { subPath ~= application; } string roamingAppData = buildPath(GetKnownFolder(FOLDERID_RoamingAppData), subPath); string localAppData = buildPath(GetKnownFolder(FOLDERID_LocalAppData), subPath); string configDir = buildPath(roamingAppData, "config"); return ProjectDirectories( buildPath(localAppData, "cache"), configDir, buildPath(roamingAppData, "data"), buildPath(localAppData, "data"), configDir, "", subPath ); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } /// unittest { import std.stdio : writeln; ProjectDirectories projectDirs = getProjectDirectories("net", "Sporadic Programmers", "Foo bar-baz"); writeln(projectDirs); } /// /// Provides the paths of user-facing standard directories, following /// the conventions of the operating system the library is running on. /// /// ## Examples /// /// All examples in this section are computed with a user named $(I Elq). /// /// Example of `UserDirectories#audioDir` value in different operating systems: /// /// $(UL /// $(LI $(B Posix): `/home/elq/Music`) /// $(LI $(B macOS): $(RED Platform not supported.)) /// $(LI $(B Windows): `C:\Users\Elq\Music`) /// ) /// struct UserDirectories { /// /// The path to the user's home directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$HOME`) /// $(TD /home/elq) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Profile`) /// $(TD C:\Users\Elq) /// ) /// ) /// immutable string homeDir; /// /// The path to the user's audio directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_MUSIC_DIR`) /// $(TD /home/elq/Music) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Music`) /// $(TD C:\Users\Elq\Music) /// ) /// ) /// immutable string audioDir; /// /// The path to the user's desktop directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DESKTOP_DIR`) /// $(TD /home/elq/Desktop) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Desktop`) /// $(TD C:\Users\Elq\Desktop) /// ) /// ) /// immutable string desktopDir; /// /// The path to the user's documents directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DOCUMENTS_DIR`) /// $(TD /home/elq/Documents) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Documents`) /// $(TD C:\Users\Elq\Documents) /// ) /// ) /// immutable string documentDir; /// /// The path to the user's download directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DOWNLOAD_DIR`) /// $(TD /home/elq/Downloads) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Downloads`) /// $(TD C:\Users\Elq\Downloads) /// ) /// ) /// immutable string downloadDir; /// /// The path to the user's fonts directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_DATA_HOME/fonts` (fallback: `$HOME/.local/share/fonts`)) /// $(TD /home/elq/.local/share/fonts) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD N/A) /// $(TD `""`) /// ) /// ) /// immutable string fontDir; /// /// The path to the user's pictures directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_PICTURES_DIR`) /// $(TD /home/elq/Pictures) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Pictures`) /// $(TD C:\Users\Elq\Pictures) /// ) /// ) /// immutable string pictureDir; /// /// The path to the user's public directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_PUBLICSHARE_DIR`) /// $(TD /home/elq/Public) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Public`) /// $(TD C:\Users\Public) /// ) /// ) /// immutable string publicDir; /// /// The path to the user's template directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_TEMPLATES_DIR`) /// $(TD /home/elq/Templates) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Templates`) /// $(TD C:\Users\Elq\AppData\Roaming\Microsoft\Windows\Templates) /// ) /// ) /// immutable string templateDir; /// /// The path to the user's video directory. /// /// $(TABLE /// $(TR /// $(TH Platform) /// $(TH Value) /// $(TH Example) /// ) /// $(TR /// $(TD Posix) /// $(TD `$XDG_VIDEOS_DIR`) /// $(TD /home/elq/Videos) /// ) /// $(TR /// $(TD macOS) /// $(TD $(RED Platform not supported)) /// $(TD $(RED Platform not supported)) /// ) /// $(TR /// $(TD Windows) /// $(TD `FOLDERID_Videos`) /// $(TD C:\Users\Elq\Videos) /// ) /// ) /// immutable string videoDir; string toString() const @safe pure nothrow { version (OSX) { enum platform = "OSX"; } else version (Posix) { enum platform = "Posix"; } else version (Windows) { enum platform = "Windows"; } return "UserDirectories(" ~ platform ~ ")\n" ~ " homeDir = '" ~ homeDir ~ "'\n" ~ " audioDir = '" ~ audioDir ~ "'\n" ~ " desktopDir = '" ~ desktopDir ~ "'\n" ~ " documentDir = '" ~ documentDir ~ "'\n" ~ " downloadDir = '" ~ downloadDir ~ "'\n" ~ " fontDir = '" ~ fontDir ~ "'\n" ~ " pictureDir = '" ~ pictureDir ~ "'\n" ~ " publicDir = '" ~ publicDir ~ "'\n" ~ " templateDir = '" ~ templateDir ~ "'\n" ~ " videoDir = '" ~ videoDir ~ "'\n"; } } /// /// Get a new instance of `UserDirectories`. /// /// The instance is an immutable snapshop of the current state of the /// system at the time this function was called. Subsequent changes /// to the state of the system are not reflected in instances created /// prior to such a change. /// nothrow UserDirectories getUserDirectories() { version (OSX) { // Support will be added. static assert(false, "mlib.directories: Unsupported operating system."); } else version (Posix) { // Fallbacks are from on // https://cgit.freedesktop.org/xdg/xdg-user-dirs/tree/user-dirs.defaults return UserDirectories( posixHome(), xdgDir("MUSIC", buildPath(posixHome(), "Music")), xdgDir("DESKTOP", buildPath(posixHome(), "Desktop")), xdgDir("DOCUMENTS", buildPath(posixHome(), "Documents")), xdgDir("DOWNLOAD", buildPath(posixHome(), "Downloads")), buildPath(xdgData(), "fonts"), xdgDir("PICTURES", buildPath(posixHome(), "Pictures")), xdgDir("PUBLICSHARE", buildPath(posixHome(), "Public")), xdgDir("TEMPLATES", buildPath(posixHome(), "Templates")), xdgDir("VIDEOS", buildPath(posixHome(), "Videos")) ); } else version (Windows) { return UserDirectories( windowsHome(), GetKnownFolder(FOLDERID_Music), GetKnownFolder(FOLDERID_Desktop), GetKnownFolder(FOLDERID_Documents), GetKnownFolder(FOLDERID_Downloads), "", GetKnownFolder(FOLDERID_Pictures), GetKnownFolder(FOLDERID_Public), GetKnownFolder(FOLDERID_Templates), GetKnownFolder(FOLDERID_Videos) ); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } /// unittest { import std.stdio : writeln; UserDirectories userDirs = getUserDirectories(); writeln(userDirs); } /++ Return a DirEntry pointing to `dt`. If the folder couldn't be found, an empty DirEntry is returned. Deprecated: Use the new `getBaseDirectories`, `getProjectDirectories`, and `getUserDirectories` functions. This will be removed in version 0.3.0. Examples: ---- import directories; import std.stdio; int main() { DirEntry userConfDir = open(Directory.config); /* Handle error */ if (0 == userConfDir.name.length) { stderr.writeln("Failed to find user config directory."); return 1; } writefln("User config directory is %s", userConfDir.name); return 0; } ---- +/ deprecated("Use new get{Base,Project,User}Directories functions (remove 0.3.0)") public DirEntry open(Directory dt) nothrow// @safe { /* * The Posix version covers a few operating systems, if your uses an * an alternative to XDG, let me know. */ version (Posix) enum supported = true; else version (Windows) enum supported = true; else enum supported = false; static if (false == supported) { static assert(0, "Unsupported platform."); } immutable string path = getPath(dt); if (path is null) return DirEntry(); try { return DirEntry(path); } catch (Exception e) { return DirEntry(); } } /// unittest { import std.conv : to; import std.stdio : stderr; DirEntry emptyDir; foreach(dir; Directory.min..Directory.max) { string dirAsString = to!string(dir); DirEntry dirEntry = open(dir); version (Windows) { // Neither of these directories are supported on Windows. if (dir == Directory.state || dir == Directory.runtime) continue; } assert(emptyDir != dirEntry, "Failed to open(Directory." ~ dirAsString ~ ")"); stderr.writefln("Successfully opened Directory.%s: %s", dirAsString, dirEntry.name()); } } private: immutable(string) getPath(in Directory dt) nothrow @safe { switch (dt) { case Directory.home: return home(); case Directory.data: return data(); case Directory.config: return config(); case Directory.cache: return cache(); case Directory.state: return state(); case Directory.runtime: return runtime(); default: assert(false, "Unsupported Directory"); } } immutable(string) home() nothrow @trusted { version (Posix) { return posixHome(); } else version (Windows) { return windowsHome(); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } immutable(string) data() nothrow @safe { version (Posix) { return xdgData(); } else version (Windows) { return windowsRoamingData(); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } immutable(string) config() nothrow @safe { version (Posix) { return xdgConfig(); } else version (Windows) { return GetKnownFolder(FOLDERID_RoamingAppData); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } /* TODO: xdgState() nothrow @safe */ immutable(string) cache() nothrow @safe { version (Posix) { return xdgCache(); } else version (Windows) { return GetKnownFolder(FOLDERID_LocalAppData); } else { static assert(false, "mlib.directories: Unsupported operating system."); } } immutable(string) state() nothrow @safe { version (Windows) { return ""; } else { import std.path : buildPath, isAbsolute; import std.process : environment; string stateEnvValue; try { stateEnvValue = environment.get("XDG_STATE_HOME"); } catch (Exception e) { stateEnvValue = null; } if (stateEnvValue is null || false == stateEnvValue.isAbsolute) stateEnvValue = buildPath(home(), ".local", "state"); return stateEnvValue; } } /// /// Defines the base directory relative to which user-specific non-essential /// runtime files and other file objects (such as sockets, named pipes, ...) /// should be stored. /// /// This will return `null` if `XDG_RUNTIME_DIR` is not set, meaning that /// the application should fall back to a replacement directory with /// similar capabilities. /// immutable(string) runtime() nothrow @safe { version (Posix) { return xdgRuntime(""); } else version (Windows) { return ""; } else { static assert(false, "mlib.directories: Unsupported operating system."); } } version (Posix) { import std.path : buildPath, isAbsolute; import std.process : environment; import std.string : empty; immutable(string) posixHome() nothrow { string homeE; try { homeE = environment.get("HOME"); } catch (Exception e) { homeE = null; } if (homeE is null) { import std.string : fromStringz; const(char)* pwdHome = _posix_fallback_home(); if (pwdHome !is null) homeE = cast(string)(pwdHome.fromStringz).dup; if (false == homeE.isAbsolute) homeE = null; } return homeE; } /// /// Retrieve the XDG User Directory for the specified $(I dirName). /// /// This will attempt to read the XDG_$(I dirName)_DIR environment /// variable. If it is not found, then we attempt to call the /// xdg-user-dir program. Should this not be installed, then /// the result of $(I fallback) (if provided) is returned or the /// empty string. /// /// Please note: If xdg-user-dir is installed, then $(I fallback) /// will never be called. xdg-user-dir always succeeds and will /// return the user's home directory if $(I dirName) isn't a valid /// value. /// /// Params: /// dirName = The XDG Directory name. /// fallback = The fallback to use if the environment variable /// is not set and xdg-user-dir is not installed. /// /// nothrow string xdgDir(string dirName, lazy string fallback = null) { import std.process : execute; import std.string : strip; string varValue; try { varValue = environment.get("XDG_" ~ dirName ~ "_DIR"); } catch (Exception) { varValue = ""; } try { if (empty(varValue)) { auto xdgRes = execute(["xdg-user-dir", dirName]); if (xdgRes.status == 0) { return strip(xdgRes.output); } } } catch (Exception) { varValue = ""; } try { if (null !is fallback) { varValue = fallback(); } } catch (Exception) { varValue = ""; } return varValue; } string xdgCache() nothrow @safe { string cacheDir; try { cacheDir = environment.get("XDG_CACHE_HOME"); } catch (Exception) { cacheDir = ""; } if (empty(cacheDir) || !isAbsolute(cacheDir)) { cacheDir = buildPath(home(), ".cache"); } return cacheDir; } string xdgConfig() nothrow @safe { string configDir; try { configDir = environment.get("XDG_CONFIG_HOME"); } catch (Exception) { configDir = ""; } if (empty(configDir) || !isAbsolute(configDir)) configDir = buildPath(home(), ".config"); return configDir; } string xdgData() nothrow @safe { import std.path : buildPath, isAbsolute; import std.process : environment; string dataE; try { dataE = environment.get("XDG_DATA_HOME"); } catch (Exception e) { dataE = null; } if (dataE is null || false == dataE.isAbsolute) dataE = buildPath(home(), ".local", "share"); return dataE; } string xdgRuntime(string subPath = null) nothrow @safe { string runtimeDir; try { runtimeDir = environment.get("XDG_RUNTIME_DIR"); } catch (Exception e) { return ""; } if (subPath !is null) { runtimeDir = buildPath(runtimeDir, subPath); } return runtimeDir; } /* Unit tests */ unittest { import std.process : environment; import std.path : buildPath; /* * Environment Variables (for checking against) */ immutable string homeE = environment.get("HOME"); immutable string configE = environment.get("XDG_CONFIG_HOME", buildPath(homeE, ".config")); immutable string dataE = environment.get("XDG_DATA_HOME", buildPath(homeE, ".local", "share")); immutable string cacheE = environment.get("XDG_CACHE_HOME", buildPath(homeE, ".cache")); // Compare against directories.d assert(homeE == open(Directory.home)); assert(configE == open(Directory.config)); assert(dataE == open(Directory.data)); assert(cacheE == open(Directory.cache)); } /* Helpers */ const(char)* _posix_fallback_home() nothrow @trusted { import core.stdc.string : strdup; passwd* pw; char* home; setpwent(); pw = getpwuid(getuid()); endpwent(); if (pw is null || pw.pw_dir is null) return null; home = strdup(pw.pw_dir); return home; } @system @nogc extern (C) nothrow { /* */ alias gid_t = uint; alias uid_t = uint; /* */ struct passwd { /// Username char* pw_name; /// Hashed passphrase, if shadow database is not in use char* pw_password; /// User ID uid_t pw_uid; /// Group ID gid_t pw_gid; /// "Real" name char* pw_gecos; /// Home directory char* pw_dir; /// Shell program char* pw_shell; } /// Rewind the user database stream extern void setpwent(); /// Close the user database stream extern void endpwent(); /// Retrieve the user database entry for the given user ID extern passwd* getpwuid(uid_t uid); /* */ /// Returns the real user ID of the calling process. extern uid_t getuid(); } } // end of version (Posix) version (Windows) { import std.path : buildPath; import std.process : environment; import std.string : empty; pragma(lib, "ole32"); string windowsHome() nothrow { string home; try { home = environment["USERPROFILE"]; } catch (Exception) { home = ""; } if (false == empty(home)) { return home; } try { scope homeDrive = environment["HOMEDRIVE"]; scope homePath = environment["HOMEPATH"]; home = homeDrive ~ homePath; } catch (Exception) { home = ""; } if (false == empty(home)) { return home; } return GetKnownFolder(FOLDERID_Profile); } string windowsRoamingData() nothrow @trusted { return GetKnownFolder(FOLDERID_RoamingAppData); } string windowsLocalData() nothrow @trusted { return GetKnownFolder(FOLDERID_LocalAppData); } /* Helpers */ import core.sys.windows.basetyps; import core.sys.windows.objbase; import core.sys.windows.windef; import core.sys.windows.winnls; alias KNOWNFOLDERID = GUID; alias REFKNOWNFOLDERID = KNOWNFOLDERID*; extern(C) nothrow @nogc @system HRESULT SHGetKnownFolderPath(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); // {B4BFCC3A-DB2C-424C-B029-7FE99A87C641} GUID FOLDERID_Desktop = GUID(0xB4BFCC3A, 0xDB2C, 0x424C, [0xB0, 0x29, 0x7F, 0xE9, 0x9A, 0x87, 0xC6, 0x41]); // {FDD39AD0-238F-46AF-ADB4-6C85480369C7} GUID FOLDERID_Documents = GUID(0xFDD39AD0, 0x238F, 0x46AF, [0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7]); // {A63293E8-664E-48DB-A079-DF759E0509F7} GUID FOLDERID_Templates = GUID(0xA63293E8, 0x664E, 0x48DB, [0xA0, 0x79, 0xDF, 0x75, 0x9E, 0x05, 0x09, 0xF7]); // {3EB685DB-65F9-4CF6-A03A-E3EF65729F3D} GUID FOLDERID_RoamingAppData = GUID(0x3EB685DB, 0x65F9, 0x4CF6, [0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D]); // {F1B32785-6FBA-4FCF-9D55-7B8E7F157091} GUID FOLDERID_LocalAppData = GUID(0xF1B32785, 0x6FBA, 0x4FCF, [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91]); // {5E6C858F-0E22-4760-9AFE-EA3317B67173} GUID FOLDERID_Profile = GUID(0x5E6C858F, 0x0E22, 0x4760, [0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73]); // {33E28130-4E1E-4676-835A-98395C3BC3BB} GUID FOLDERID_Pictures = GUID(0x33E28130, 0x4E1E, 0x4676, [0x83, 0x5A, 0x98, 0x39, 0x5C, 0x3B, 0xC3, 0xBB]); // {4BD8D571-6D19-48D3-BE97-422220080E43} GUID FOLDERID_Music = GUID(0x4BD8D571, 0x6D19, 0x48D3, [0xBE, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0E, 0x43]); // {18989B1D-99B5-455B-841C-AB7C74E4DDFC} GUID FOLDERID_Videos = GUID(0x18989B1D, 0x99B5, 0x455B, [0x84, 0x1C, 0xAB, 0x7C, 0x74, 0xE4, 0xDD, 0xFC]); // {DFDF76A2-C82A-4D63-906A-5644AC457385} GUID FOLDERID_Public = GUID(0xDFDF76A2, 0xC82A, 0x4D63, [0x90, 0x6A, 0x56, 0x44, 0xAC, 0x45, 0x73, 0x85]); // {374DE290-123F-4565-9164-39C4925E467B} GUID FOLDERID_Downloads = GUID(0x374de290, 0x123f, 0x4565, [0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b]); string GetKnownFolder(KNOWNFOLDERID rdif) nothrow @trusted { PWSTR path; HRESULT status; ULONG bufferSize = 0; status = SHGetKnownFolderPath(&rdif, 0, null, &path); if (status != S_OK) { CoTaskMemFree(path); return ""; } scope(exit) CoTaskMemFree(path); UnicodeToAnsiSize(path, bufferSize); // -1 to remove the null character which D doesn't use char[] str = new char[bufferSize - 1]; WideCharToMultiByte(CP_UTF8, 0, path, -1, str.ptr, bufferSize - 1, null, null); return cast(string)str; } void UnicodeToAnsiSize(in PWCHAR UnicodeString, out ULONG AnsiSizeInBytes) nothrow { AnsiSizeInBytes = WideCharToMultiByte(CP_UTF8, 0, UnicodeString, -1, null, 0, null, null); } } // end of version (Windows)