1"""
2This module is based on a rox module (LGPL):
3
4http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9&view=log
5
6The freedesktop.org Base Directory specification provides a way for
7applications to locate shared data and configuration:
8
9 http://standards.freedesktop.org/basedir-spec/
10
11(based on version 0.6)
12
13This module can be used to load and save from and to these directories.
14
15Typical usage:
16
17 from rox import basedir
18
19 for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'):
20 print "Load settings from", dir
21
22 dir = basedir.save_config_path('mydomain.org', 'MyProg')
23 print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2"
24
25Note: see the rox.Options module for a higher-level API for managing options.
26"""
27
28import os, stat
29
30_home = os.path.expanduser('~')
31xdg_data_home = os.environ.get('XDG_DATA_HOME') or \
32 os.path.join(_home, '.local', 'share')
33
34xdg_data_dirs = [xdg_data_home] + \
35 (os.environ.get('XDG_DATA_DIRS') or '/usr/local/share:/usr/share').split(':')
36
37xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \
38 os.path.join(_home, '.config')
39
40xdg_config_dirs = [xdg_config_home] + \
41 (os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg').split(':')
42
43xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
44 os.path.join(_home, '.cache')
45
46xdg_state_home = os.environ.get('XDG_STATE_HOME') or \
47 os.path.join(_home, '.local', 'state')
48
49xdg_data_dirs = [x for x in xdg_data_dirs if x]
50xdg_config_dirs = [x for x in xdg_config_dirs if x]
51
52def save_config_path(*resource):
53 """Ensure ``$XDG_CONFIG_HOME/<resource>/`` exists, and return its path.
54 'resource' should normally be the name of your application. Use this
55 when saving configuration settings.
56 """
57 resource = os.path.join(*resource)
58 assert not resource.startswith('/')
59 path = os.path.join(xdg_config_home, resource)
60 if not os.path.isdir(path):
61 os.makedirs(path, 0o700)
62 return path
63
64def save_data_path(*resource):
65 """Ensure ``$XDG_DATA_HOME/<resource>/`` exists, and return its path.
66 'resource' should normally be the name of your application or a shared
67 resource. Use this when saving or updating application data.
68 """
69 resource = os.path.join(*resource)
70 assert not resource.startswith('/')
71 path = os.path.join(xdg_data_home, resource)
72 if not os.path.isdir(path):
73 os.makedirs(path)
74 return path
75
76def save_cache_path(*resource):
77 """Ensure ``$XDG_CACHE_HOME/<resource>/`` exists, and return its path.
78 'resource' should normally be the name of your application or a shared
79 resource."""
80 resource = os.path.join(*resource)
81 assert not resource.startswith('/')
82 path = os.path.join(xdg_cache_home, resource)
83 if not os.path.isdir(path):
84 os.makedirs(path)
85 return path
86
87def save_state_path(*resource):
88 """Ensure ``$XDG_STATE_HOME/<resource>/`` exists, and return its path.
89 'resource' should normally be the name of your application or a shared
90 resource."""
91 resource = os.path.join(*resource)
92 assert not resource.startswith('/')
93 path = os.path.join(xdg_state_home, resource)
94 if not os.path.isdir(path):
95 os.makedirs(path)
96 return path
97
98def load_config_paths(*resource):
99 """Returns an iterator which gives each directory named 'resource' in the
100 configuration search path. Information provided by earlier directories should
101 take precedence over later ones, and the user-specific config dir comes
102 first."""
103 resource = os.path.join(*resource)
104 for config_dir in xdg_config_dirs:
105 path = os.path.join(config_dir, resource)
106 if os.path.exists(path): yield path
107
108def load_first_config(*resource):
109 """Returns the first result from load_config_paths, or None if there is nothing
110 to load."""
111 for x in load_config_paths(*resource):
112 return x
113 return None
114
115def load_data_paths(*resource):
116 """Returns an iterator which gives each directory named 'resource' in the
117 application data search path. Information provided by earlier directories
118 should take precedence over later ones."""
119 resource = os.path.join(*resource)
120 for data_dir in xdg_data_dirs:
121 path = os.path.join(data_dir, resource)
122 if os.path.exists(path): yield path
123
124def get_runtime_dir(strict=True):
125 """Returns the value of $XDG_RUNTIME_DIR, a directory path.
126
127 This directory is intended for 'user-specific non-essential runtime files
128 and other file objects (such as sockets, named pipes, ...)', and
129 'communication and synchronization purposes'.
130
131 As of late 2012, only quite new systems set $XDG_RUNTIME_DIR. If it is not
132 set, with ``strict=True`` (the default), a KeyError is raised. With
133 ``strict=False``, PyXDG will create a fallback under /tmp for the current
134 user. This fallback does *not* provide the same guarantees as the
135 specification requires for the runtime directory.
136
137 The strict default is deliberately conservative, so that application
138 developers can make a conscious decision to allow the fallback.
139 """
140 try:
141 return os.environ['XDG_RUNTIME_DIR']
142 except KeyError:
143 if strict:
144 raise
145
146 import getpass
147 fallback = '/tmp/pyxdg-runtime-dir-fallback-' + getpass.getuser()
148 create = False
149
150 try:
151 # This must be a real directory, not a symlink, so attackers can't
152 # point it elsewhere. So we use lstat to check it.
153 st = os.lstat(fallback)
154 except OSError as e:
155 import errno
156 if e.errno == errno.ENOENT:
157 create = True
158 else:
159 raise
160 else:
161 # The fallback must be a directory
162 if not stat.S_ISDIR(st.st_mode):
163 os.unlink(fallback)
164 create = True
165 # Must be owned by the user and not accessible by anyone else
166 elif (st.st_uid != os.getuid()) \
167 or (st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
168 os.rmdir(fallback)
169 create = True
170
171 if create:
172 os.mkdir(fallback, 0o700)
173
174 return fallback