mirror of https://github.com/buggins/dlangui.git
Remove xdgpath (for GDC build)
This commit is contained in:
parent
a0b882b18a
commit
85a334e951
|
|
@ -1,503 +0,0 @@
|
|||
/**
|
||||
* Getting XDG base directories.
|
||||
* Note: These functions are defined only on freedesktop systems.
|
||||
* Authors:
|
||||
* $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov)
|
||||
* Copyright:
|
||||
* Roman Chistokhodov, 2016
|
||||
* License:
|
||||
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
||||
* See_Also:
|
||||
* $(LINK2 https://specifications.freedesktop.org/basedir-spec/latest/index.html, XDG Base Directory Specification)
|
||||
*/
|
||||
|
||||
module xdgpaths;
|
||||
|
||||
import isfreedesktop;
|
||||
|
||||
version(D_Ddoc)
|
||||
{
|
||||
/**
|
||||
* Path to runtime user directory.
|
||||
* Returns: User's runtime directory determined by $(B XDG_RUNTIME_DIR) environment variable.
|
||||
* If directory does not exist it tries to create one with appropriate permissions. On fail returns an empty string.
|
||||
*/
|
||||
@trusted string xdgRuntimeDir() nothrow;
|
||||
|
||||
/**
|
||||
* The ordered set of non-empty base paths to search for data files, in descending order of preference.
|
||||
* Params:
|
||||
* subfolder = Subfolder which is appended to every path if not null.
|
||||
* Returns: Data directories, without user's one and with no duplicates.
|
||||
* Note: This function does not check if paths actually exist and appear to be directories.
|
||||
* See_Also: $(D xdgAllDataDirs), $(D xdgDataHome)
|
||||
*/
|
||||
@trusted string[] xdgDataDirs(string subfolder = null) nothrow;
|
||||
|
||||
/**
|
||||
* The ordered set of non-empty base paths to search for data files, in descending order of preference.
|
||||
* Params:
|
||||
* subfolder = Subfolder which is appended to every path if not null.
|
||||
* Returns: Data directories, including user's one if could be evaluated.
|
||||
* Note: This function does not check if paths actually exist and appear to be directories.
|
||||
* See_Also: $(D xdgDataDirs), $(D xdgDataHome)
|
||||
*/
|
||||
@trusted string[] xdgAllDataDirs(string subfolder = null) nothrow;
|
||||
|
||||
/**
|
||||
* The ordered set of non-empty base paths to search for configuration files, in descending order of preference.
|
||||
* Params:
|
||||
* subfolder = Subfolder which is appended to every path if not null.
|
||||
* Returns: Config directories, without user's one and with no duplicates.
|
||||
* Note: This function does not check if paths actually exist and appear to be directories.
|
||||
* See_Also: $(D xdgAllConfigDirs), $(D xdgConfigHome)
|
||||
*/
|
||||
@trusted string[] xdgConfigDirs(string subfolder = null) nothrow;
|
||||
|
||||
/**
|
||||
* The ordered set of non-empty base paths to search for configuration files, in descending order of preference.
|
||||
* Params:
|
||||
* subfolder = Subfolder which is appended to every path if not null.
|
||||
* Returns: Config directories, including user's one if could be evaluated.
|
||||
* Note: This function does not check if paths actually exist and appear to be directories.
|
||||
* See_Also: $(D xdgConfigDirs), $(D xdgConfigHome)
|
||||
*/
|
||||
@trusted string[] xdgAllConfigDirs(string subfolder = null) nothrow;
|
||||
|
||||
/**
|
||||
* The base directory relative to which user-specific data files should be stored.
|
||||
* Returns: Path to user-specific data directory or empty string on error.
|
||||
* Params:
|
||||
* subfolder = Subfolder to append to determined path.
|
||||
* shouldCreate = If path does not exist, create directory using 700 permissions (i.e. allow access only for current user).
|
||||
* See_Also: $(D xdgAllDataDirs), $(D xdgDataDirs)
|
||||
*/
|
||||
@trusted string xdgDataHome(string subfolder = null, bool shouldCreate = false) nothrow;
|
||||
|
||||
/**
|
||||
* The base directory relative to which user-specific configuration files should be stored.
|
||||
* Returns: Path to user-specific configuration directory or empty string on error.
|
||||
* Params:
|
||||
* subfolder = Subfolder to append to determined path.
|
||||
* shouldCreate = If path does not exist, create directory using 700 permissions (i.e. allow access only for current user).
|
||||
* See_Also: $(D xdgAllConfigDirs), $(D xdgConfigDirs)
|
||||
*/
|
||||
@trusted string xdgConfigHome(string subfolder = null, bool shouldCreate = false) nothrow;
|
||||
|
||||
/**
|
||||
* The base directory relative to which user-specific non-essential files should be stored.
|
||||
* Returns: Path to user-specific cache directory or empty string on error.
|
||||
* Params:
|
||||
* subfolder = Subfolder to append to determined path.
|
||||
* shouldCreate = If path does not exist, create directory using 700 permissions (i.e. allow access only for current user).
|
||||
*/
|
||||
@trusted string xdgCacheHome(string subfolder = null, bool shouldCreate = false) nothrow;
|
||||
}
|
||||
|
||||
static if (isFreedesktop)
|
||||
{
|
||||
private {
|
||||
import std.algorithm : splitter, map, filter, canFind;
|
||||
import std.array;
|
||||
import std.conv : octal;
|
||||
import std.exception : collectException, enforce;
|
||||
import std.file;
|
||||
import std.path : buildPath, dirName;
|
||||
import std.process : environment;
|
||||
import std.string : toStringz;
|
||||
|
||||
import core.sys.posix.unistd;
|
||||
import core.sys.posix.sys.stat;
|
||||
import core.sys.posix.sys.types;
|
||||
import core.stdc.string;
|
||||
import core.stdc.errno;
|
||||
|
||||
static if (is(typeof({import std.string : fromStringz;}))) {
|
||||
import std.string : fromStringz;
|
||||
} else { //own fromStringz implementation for compatibility reasons
|
||||
@system static pure inout(char)[] fromStringz(inout(char)* cString) {
|
||||
return cString ? cString[0..strlen(cString)] : null;
|
||||
}
|
||||
}
|
||||
|
||||
enum mode_t privateMode = octal!700;
|
||||
}
|
||||
|
||||
version(unittest) {
|
||||
import std.algorithm : equal;
|
||||
|
||||
private struct EnvGuard
|
||||
{
|
||||
this(string env) {
|
||||
envVar = env;
|
||||
envValue = environment.get(env);
|
||||
}
|
||||
|
||||
~this() {
|
||||
if (envValue is null) {
|
||||
environment.remove(envVar);
|
||||
} else {
|
||||
environment[envVar] = envValue;
|
||||
}
|
||||
}
|
||||
|
||||
string envVar;
|
||||
string envValue;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] pathsFromEnvValue(string envValue, string subfolder = null) nothrow {
|
||||
string[] result;
|
||||
try {
|
||||
foreach(path; splitter(envValue, ':').filter!(p => !p.empty).map!(p => buildPath(p, subfolder))) {
|
||||
if (path[$-1] == '/') {
|
||||
path = path[0..$-1];
|
||||
}
|
||||
if (!result.canFind(path)) {
|
||||
result ~= path;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
assert(pathsFromEnvValue("") == (string[]).init);
|
||||
assert(pathsFromEnvValue(":") == (string[]).init);
|
||||
assert(pathsFromEnvValue("::") == (string[]).init);
|
||||
|
||||
assert(pathsFromEnvValue("path1:path2") == ["path1", "path2"]);
|
||||
assert(pathsFromEnvValue("path1:") == ["path1"]);
|
||||
assert(pathsFromEnvValue("path1/") == ["path1"]);
|
||||
assert(pathsFromEnvValue("path1/:path1") == ["path1"]);
|
||||
assert(pathsFromEnvValue("path2:path1:path2") == ["path2", "path1"]);
|
||||
}
|
||||
|
||||
private string[] pathsFromEnv(string envVar, string subfolder = null) nothrow {
|
||||
string envValue;
|
||||
collectException(environment.get(envVar), envValue);
|
||||
return pathsFromEnvValue(envValue, subfolder);
|
||||
}
|
||||
|
||||
private bool ensureExists(string dir) nothrow
|
||||
{
|
||||
bool ok;
|
||||
try {
|
||||
ok = dir.exists;
|
||||
if (!ok) {
|
||||
mkdirRecurse(dir.dirName);
|
||||
ok = mkdir(dir.toStringz, privateMode) == 0;
|
||||
} else {
|
||||
ok = dir.isDir;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import std.file;
|
||||
import std.stdio;
|
||||
|
||||
string temp = tempDir();
|
||||
if (temp.length) {
|
||||
string testDir = buildPath(temp, "xdgpaths-unittest-tempdir");
|
||||
string testFile = buildPath(testDir, "touched");
|
||||
string testSubDir = buildPath(testDir, "subdir");
|
||||
try {
|
||||
mkdir(testDir);
|
||||
File(testFile, "w");
|
||||
assert(!ensureExists(testFile));
|
||||
enforce(ensureExists(testSubDir));
|
||||
} catch(Exception e) {
|
||||
|
||||
} finally {
|
||||
collectException(rmdir(testSubDir));
|
||||
collectException(remove(testFile));
|
||||
collectException(rmdir(testDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string xdgBaseDir(string envvar, string fallback, string subfolder = null, bool shouldCreate = false) nothrow {
|
||||
string dir;
|
||||
collectException(environment.get(envvar), dir);
|
||||
if (dir.length == 0) {
|
||||
string home;
|
||||
collectException(environment.get("HOME"), home);
|
||||
dir = home.length ? buildPath(home, fallback) : null;
|
||||
}
|
||||
|
||||
if (dir.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shouldCreate) {
|
||||
if (ensureExists(dir)) {
|
||||
if (subfolder.length) {
|
||||
string path = buildPath(dir, subfolder);
|
||||
try {
|
||||
if (!path.exists) {
|
||||
mkdirRecurse(path);
|
||||
}
|
||||
return path;
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
} else {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return buildPath(dir, subfolder);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
version(unittest) {
|
||||
void testXdgBaseDir(string envVar, string fallback) {
|
||||
auto homeGuard = EnvGuard("HOME");
|
||||
auto dataHomeGuard = EnvGuard(envVar);
|
||||
|
||||
auto newHome = "/home/myuser";
|
||||
auto newDataHome = "/home/myuser/data";
|
||||
|
||||
environment[envVar] = newDataHome;
|
||||
assert(xdgBaseDir(envVar, fallback) == newDataHome);
|
||||
assert(xdgBaseDir(envVar, fallback, "applications") == buildPath(newDataHome, "applications"));
|
||||
|
||||
environment.remove(envVar);
|
||||
environment["HOME"] = newHome;
|
||||
assert(xdgBaseDir(envVar, fallback) == buildPath(newHome, fallback));
|
||||
assert(xdgBaseDir(envVar, fallback, "icons") == buildPath(newHome, fallback, "icons"));
|
||||
|
||||
environment.remove("HOME");
|
||||
assert(xdgBaseDir(envVar, fallback).empty);
|
||||
assert(xdgBaseDir(envVar, fallback, "mime").empty);
|
||||
}
|
||||
}
|
||||
|
||||
@trusted string[] xdgDataDirs(string subfolder = null) nothrow
|
||||
{
|
||||
auto result = pathsFromEnv("XDG_DATA_DIRS", subfolder);
|
||||
if (result.length) {
|
||||
return result;
|
||||
} else {
|
||||
return [buildPath("/usr/local/share", subfolder), buildPath("/usr/share", subfolder)];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto dataDirsGuard = EnvGuard("XDG_DATA_DIRS");
|
||||
|
||||
auto newDataDirs = ["/usr/local/data", "/usr/data"];
|
||||
|
||||
environment["XDG_DATA_DIRS"] = "/usr/local/data:/usr/data:/usr/local/data/:/usr/data/";
|
||||
assert(xdgDataDirs() == newDataDirs);
|
||||
assert(equal(xdgDataDirs("applications"), newDataDirs.map!(p => buildPath(p, "applications"))));
|
||||
|
||||
environment.remove("XDG_DATA_DIRS");
|
||||
assert(xdgDataDirs() == ["/usr/local/share", "/usr/share"]);
|
||||
assert(equal(xdgDataDirs("icons"), ["/usr/local/share", "/usr/share"].map!(p => buildPath(p, "icons"))));
|
||||
}
|
||||
|
||||
@trusted string[] xdgAllDataDirs(string subfolder = null) nothrow
|
||||
{
|
||||
string dataHome = xdgDataHome(subfolder);
|
||||
string[] dataDirs = xdgDataDirs(subfolder);
|
||||
if (dataHome.length) {
|
||||
return dataHome ~ dataDirs;
|
||||
} else {
|
||||
return dataDirs;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto homeGuard = EnvGuard("HOME");
|
||||
auto dataHomeGuard = EnvGuard("XDG_DATA_HOME");
|
||||
auto dataDirsGuard = EnvGuard("XDG_DATA_DIRS");
|
||||
|
||||
auto newDataHome = "/home/myuser/data";
|
||||
auto newDataDirs = ["/usr/local/data", "/usr/data"];
|
||||
environment["XDG_DATA_HOME"] = newDataHome;
|
||||
environment["XDG_DATA_DIRS"] = "/usr/local/data:/usr/data";
|
||||
|
||||
assert(xdgAllDataDirs() == newDataHome ~ newDataDirs);
|
||||
|
||||
environment.remove("XDG_DATA_HOME");
|
||||
environment.remove("HOME");
|
||||
|
||||
assert(xdgAllDataDirs() == newDataDirs);
|
||||
}
|
||||
|
||||
@trusted string[] xdgConfigDirs(string subfolder = null) nothrow
|
||||
{
|
||||
auto result = pathsFromEnv("XDG_CONFIG_DIRS", subfolder);
|
||||
if (result.length) {
|
||||
return result;
|
||||
} else {
|
||||
return [buildPath("/etc/xdg", subfolder)];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto dataConfigGuard = EnvGuard("XDG_CONFIG_DIRS");
|
||||
|
||||
auto newConfigDirs = ["/usr/local/config", "/usr/config"];
|
||||
|
||||
environment["XDG_CONFIG_DIRS"] = "/usr/local/config:/usr/config";
|
||||
assert(xdgConfigDirs() == newConfigDirs);
|
||||
assert(equal(xdgConfigDirs("menus"), newConfigDirs.map!(p => buildPath(p, "menus"))));
|
||||
|
||||
environment.remove("XDG_CONFIG_DIRS");
|
||||
assert(xdgConfigDirs() == ["/etc/xdg"]);
|
||||
assert(equal(xdgConfigDirs("autostart"), ["/etc/xdg"].map!(p => buildPath(p, "autostart"))));
|
||||
}
|
||||
|
||||
@trusted string[] xdgAllConfigDirs(string subfolder = null) nothrow
|
||||
{
|
||||
string configHome = xdgConfigHome(subfolder);
|
||||
string[] configDirs = xdgConfigDirs(subfolder);
|
||||
if (configHome.length) {
|
||||
return configHome ~ configDirs;
|
||||
} else {
|
||||
return configDirs;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto homeGuard = EnvGuard("HOME");
|
||||
auto configHomeGuard = EnvGuard("XDG_CONFIG_HOME");
|
||||
auto configDirsGuard = EnvGuard("XDG_CONFIG_DIRS");
|
||||
|
||||
auto newConfigHome = "/home/myuser/data";
|
||||
environment["XDG_CONFIG_HOME"] = newConfigHome;
|
||||
auto newConfigDirs = ["/usr/local/data", "/usr/data"];
|
||||
environment["XDG_CONFIG_DIRS"] = "/usr/local/data:/usr/data";
|
||||
|
||||
assert(xdgAllConfigDirs() == newConfigHome ~ newConfigDirs);
|
||||
|
||||
environment.remove("XDG_CONFIG_HOME");
|
||||
environment.remove("HOME");
|
||||
|
||||
assert(xdgAllConfigDirs() == newConfigDirs);
|
||||
}
|
||||
|
||||
@trusted string xdgDataHome(string subfolder = null, bool shouldCreate = false) nothrow {
|
||||
return xdgBaseDir("XDG_DATA_HOME", ".local/share", subfolder, shouldCreate);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
testXdgBaseDir("XDG_DATA_HOME", ".local/share");
|
||||
}
|
||||
|
||||
@trusted string xdgConfigHome(string subfolder = null, bool shouldCreate = false) nothrow {
|
||||
return xdgBaseDir("XDG_CONFIG_HOME", ".config", subfolder, shouldCreate);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
testXdgBaseDir("XDG_CONFIG_HOME", ".config");
|
||||
}
|
||||
|
||||
@trusted string xdgCacheHome(string subfolder = null, bool shouldCreate = false) nothrow {
|
||||
return xdgBaseDir("XDG_CACHE_HOME", ".cache", subfolder, shouldCreate);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
testXdgBaseDir("XDG_CACHE_HOME", ".cache");
|
||||
}
|
||||
|
||||
version(XdgPathsRuntimeDebug) {
|
||||
private import std.stdio;
|
||||
}
|
||||
|
||||
@trusted string xdgRuntimeDir() nothrow // Do we need it on BSD systems?
|
||||
{
|
||||
import std.exception : assumeUnique;
|
||||
import core.sys.posix.pwd;
|
||||
|
||||
try { //one try to rule them all and for compatibility reasons
|
||||
const uid_t uid = getuid();
|
||||
string runtime;
|
||||
collectException(environment.get("XDG_RUNTIME_DIR"), runtime);
|
||||
|
||||
if (!runtime.length) {
|
||||
passwd* pw = getpwuid(uid);
|
||||
|
||||
try {
|
||||
if (pw && pw.pw_name) {
|
||||
runtime = tempDir() ~ "/runtime-" ~ assumeUnique(fromStringz(pw.pw_name));
|
||||
|
||||
if (!(runtime.exists && runtime.isDir)) {
|
||||
if (mkdir(runtime.toStringz, privateMode) != 0) {
|
||||
version(XdgPathsRuntimeDebug) stderr.writefln("Failed to create runtime directory %s: %s", runtime, fromStringz(strerror(errno)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
version(XdgPathsRuntimeDebug) stderr.writeln("Failed to get user name to create runtime directory");
|
||||
return null;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
version(XdgPathsRuntimeDebug) collectException(stderr.writefln("Error when creating runtime directory: %s", e.msg));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
stat_t statbuf;
|
||||
stat(runtime.toStringz, &statbuf);
|
||||
if (statbuf.st_uid != uid) {
|
||||
version(XdgPathsRuntimeDebug) collectException(stderr.writeln("Wrong ownership of runtime directory %s, %d instead of %d", runtime, statbuf.st_uid, uid));
|
||||
return null;
|
||||
}
|
||||
if ((statbuf.st_mode & octal!777) != privateMode) {
|
||||
version(XdgPathsRuntimeDebug) collectException(stderr.writefln("Wrong permissions on runtime directory %s, %o instead of %o", runtime, statbuf.st_mode, privateMode));
|
||||
return null;
|
||||
}
|
||||
|
||||
return runtime;
|
||||
} catch (Exception e) {
|
||||
version(XdgPathsRuntimeDebug) collectException(stderr.writeln("Error when getting runtime directory: %s", e.msg));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
version(xdgpathsFileTest) unittest
|
||||
{
|
||||
string runtimePath = buildPath(tempDir(), "xdgpaths-runtime-test");
|
||||
try {
|
||||
collectException(std.file.rmdir(runtimePath));
|
||||
|
||||
if (mkdir(runtimePath.toStringz, privateMode) == 0) {
|
||||
auto runtimeGuard = EnvGuard("XDG_RUNTIME_DIR");
|
||||
environment["XDG_RUNTIME_DIR"] = runtimePath;
|
||||
assert(xdgRuntimeDir() == runtimePath);
|
||||
|
||||
if (chmod(runtimePath.toStringz, octal!777) == 0) {
|
||||
assert(xdgRuntimeDir() == string.init);
|
||||
}
|
||||
|
||||
std.file.rmdir(runtimePath);
|
||||
} else {
|
||||
version(XdgPathsRuntimeDebug) stderr.writeln(fromStringz(strerror(errno)));
|
||||
}
|
||||
} catch(Exception e) {
|
||||
version(XdgPathsRuntimeDebug) stderr.writeln(e.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue