AppDirsError   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 2
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# Copyright (c) 2005-2010 ActiveState Software Inc.
3
4
"""Utilities for determining application-specific dirs.
5
6
See <http://github.com/ActiveState/appdirs> for details and usage.
7
"""
8
# Dev Notes:
9
# - MSDN on where to store app data files:
10
#   http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
11
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
12
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
13
14
__version_info__ = (1, 2, 0)
15
__version__ = '.'.join(map(str, __version_info__))
16
17
18
import sys
19
import os
20
21
PY3 = sys.version_info[0] == 3
22
23
if PY3:
24
    unicode = str
25
26
class AppDirsError(Exception):
27
    pass
28
29
30
31 View Code Duplication
def user_data_dir(appname, appauthor=None, version=None, roaming=False):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
32
    r"""Return full path to the user-specific data dir for this application.
33
34
        "appname" is the name of application.
35
        "appauthor" (only required and used on Windows) is the name of the
36
            appauthor or distributing body for this application. Typically
37
            it is the owning company name.
38
        "version" is an optional version path element to append to the
39
            path. You might want to use this if you want multiple versions
40
            of your app to be able to run independently. If used, this
41
            would typically be "<major>.<minor>".
42
        "roaming" (boolean, default False) can be set True to use the Windows
43
            roaming appdata directory. That means that for users on a Windows
44
            network setup for roaming profiles, this user data will be
45
            sync'd on login. See
46
            <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
47
            for a discussion of issues.
48
49
    Typical user data directories are:
50
        Mac OS X:               ~/Library/Application Support/<AppName>
51
        Unix:                   ~/.config/<appname>    # or in $XDG_CONFIG_HOME if defined
52
        Win XP (not roaming):   C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
53
        Win XP (roaming):       C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
54
        Win 7  (not roaming):   C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
55
        Win 7  (roaming):       C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
56
57
    For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. We don't
58
    use $XDG_DATA_HOME as that data dir is mostly used at the time of
59
    installation, instead of the application adding data during runtime.
60
    Also, in practice, Linux apps tend to store their data in
61
    "~/.config/<appname>" instead of "~/.local/share/<appname>".
62
    """
63
    if sys.platform.startswith("win"):
64
        if appauthor is None:
65
            raise AppDirsError("must specify 'appauthor' on Windows")
66
        const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
67
        path = os.path.join(_get_win_folder(const), appauthor, appname)
68
    elif sys.platform == 'darwin':
69
        path = os.path.join(
70
            os.path.expanduser('~/Library/Application Support/'),
71
            appname)
72
    else:
73
        path = os.path.join(
74
            os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")),
75
            appname.lower())
76
    if version:
77
        path = os.path.join(path, version)
78
    return path
79
80
81
def site_data_dir(appname, appauthor=None, version=None):
82
    """Return full path to the user-shared data dir for this application.
83
84
        "appname" is the name of application.
85
        "appauthor" (only required and used on Windows) is the name of the
86
            appauthor or distributing body for this application. Typically
87
            it is the owning company name.
88
        "version" is an optional version path element to append to the
89
            path. You might want to use this if you want multiple versions
90
            of your app to be able to run independently. If used, this
91
            would typically be "<major>.<minor>".
92
93
    Typical user data directories are:
94
        Mac OS X:   /Library/Application Support/<AppName>
95
        Unix:       /etc/xdg/<appname>
96
        Win XP:     C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
97
        Vista:      (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
98
        Win 7:      C:\ProgramData\<AppAuthor>\<AppName>   # Hidden, but writeable on Win 7.
99
100
    For Unix, this is using the $XDG_CONFIG_DIRS[0] default.
101
102
    WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
103
    """
104
    if sys.platform.startswith("win"):
105
        if appauthor is None:
106
            raise AppDirsError("must specify 'appauthor' on Windows")
107
        path = os.path.join(_get_win_folder("CSIDL_COMMON_APPDATA"),
108
                            appauthor, appname)
109
    elif sys.platform == 'darwin':
110
        path = os.path.join(
111
            os.path.expanduser('/Library/Application Support'),
112
            appname)
113
    else:
114
        # XDG default for $XDG_CONFIG_DIRS[0]. Perhaps should actually
115
        # *use* that envvar, if defined.
116
        path = "/etc/xdg/" + appname.lower()
117
    if version:
118
        path = os.path.join(path, version)
119
    return path
120
121
122 View Code Duplication
def user_cache_dir(appname, appauthor=None, version=None, opinion=True):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
123
    r"""Return full path to the user-specific cache dir for this application.
124
125
        "appname" is the name of application.
126
        "appauthor" (only required and used on Windows) is the name of the
127
            appauthor or distributing body for this application. Typically
128
            it is the owning company name.
129
        "version" is an optional version path element to append to the
130
            path. You might want to use this if you want multiple versions
131
            of your app to be able to run independently. If used, this
132
            would typically be "<major>.<minor>".
133
        "opinion" (boolean) can be False to disable the appending of
134
            "Cache" to the base app data dir for Windows. See
135
            discussion below.
136
137
    Typical user cache directories are:
138
        Mac OS X:   ~/Library/Caches/<AppName>
139
        Unix:       ~/.cache/<appname> (XDG default)
140
        Win XP:     C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
141
        Vista:      C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
142
143
    On Windows the only suggestion in the MSDN docs is that local settings go in
144
    the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
145
    app data dir (the default returned by `user_data_dir` above). Apps typically
146
    put cache data somewhere *under* the given dir here. Some examples:
147
        ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
148
        ...\Acme\SuperApp\Cache\1.0
149
    OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
150
    This can be disabled with the `opinion=False` option.
151
    """
152
    if sys.platform.startswith("win"):
153
        if appauthor is None:
154
            raise AppDirsError("must specify 'appauthor' on Windows")
155
        path = os.path.join(_get_win_folder("CSIDL_LOCAL_APPDATA"),
156
                            appauthor, appname)
157
        if opinion:
158
            path = os.path.join(path, "Cache")
159
    elif sys.platform == 'darwin':
160
        path = os.path.join(
161
            os.path.expanduser('~/Library/Caches'),
162
            appname)
163
    else:
164
        path = os.path.join(
165
            os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')),
166
            appname.lower())
167
    if version:
168
        path = os.path.join(path, version)
169
    return path
170
171
def user_log_dir(appname, appauthor=None, version=None, opinion=True):
172
    r"""Return full path to the user-specific log dir for this application.
173
174
        "appname" is the name of application.
175
        "appauthor" (only required and used on Windows) is the name of the
176
            appauthor or distributing body for this application. Typically
177
            it is the owning company name.
178
        "version" is an optional version path element to append to the
179
            path. You might want to use this if you want multiple versions
180
            of your app to be able to run independently. If used, this
181
            would typically be "<major>.<minor>".
182
        "opinion" (boolean) can be False to disable the appending of
183
            "Logs" to the base app data dir for Windows, and "log" to the
184
            base cache dir for Unix. See discussion below.
185
186
    Typical user cache directories are:
187
        Mac OS X:   ~/Library/Logs/<AppName>
188
        Unix:       ~/.cache/<appname>/log  # or under $XDG_CACHE_HOME if defined
189
        Win XP:     C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
190
        Vista:      C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
191
192
    On Windows the only suggestion in the MSDN docs is that local settings
193
    go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
194
    examples of what some windows apps use for a logs dir.)
195
196
    OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
197
    value for Windows and appends "log" to the user cache dir for Unix.
198
    This can be disabled with the `opinion=False` option.
199
    """
200
    if sys.platform == "darwin":
201
        path = os.path.join(
202
            os.path.expanduser('~/Library/Logs'),
203
            appname)
204
    elif sys.platform == "win32":
205
        path = user_data_dir(appname, appauthor, version); version = False
206
        if opinion:
207
            path = os.path.join(path, "Logs")
208
    else:
209
        path = user_cache_dir(appname, appauthor, version); version = False
210
        if opinion:
211
            path = os.path.join(path, "log")
212
    if version:
213
        path = os.path.join(path, version)
214
    return path
215
216
class EnvAppDirs(object):
217
    def __init__(self, appname, appauthor, root_path):
218
        self.appname = appname
219
        self.appauthor = appauthor
220
        self.root_path = root_path
221
    @property
222
    def user_data_dir(self):
223
        return os.path.join(self.root_path, 'data')
224
    @property
225
    def site_data_dir(self):
226
        return os.path.join(self.root_path, 'data')
227
    @property
228
    def user_cache_dir(self):
229
        return os.path.join(self.root_path, 'cache')
230
231
    @property
232
    def user_log_dir(self):
233
        return os.path.join(self.root_path, 'log')
234
235
236
class AppDirs(object):
237
    """Convenience wrapper for getting application dirs."""
238
    def __init__(self, appname, appauthor, version=None, roaming=False):
239
        self.appname = appname
240
        self.appauthor = appauthor
241
        self.version = version
242
        self.roaming = roaming
243
    @property
244
    def user_data_dir(self):
245
        return user_data_dir(self.appname, self.appauthor,
246
            version=self.version, roaming=self.roaming)
247
    @property
248
    def site_data_dir(self):
249
        return site_data_dir(self.appname, self.appauthor,
250
            version=self.version)
251
    @property
252
    def user_cache_dir(self):
253
        return user_cache_dir(self.appname, self.appauthor,
254
            version=self.version)
255
    @property
256
    def user_log_dir(self):
257
        return user_log_dir(self.appname, self.appauthor,
258
            version=self.version)
259
260
261
262
263
#---- internal support stuff
264
265
def _get_win_folder_from_registry(csidl_name):
266
    """This is a fallback technique at best. I'm not sure if using the
267
    registry for this guarantees us the correct answer for all CSIDL_*
268
    names.
269
    """
270
    import _winreg
271
272
    shell_folder_name = {
273
        "CSIDL_APPDATA": "AppData",
274
        "CSIDL_COMMON_APPDATA": "Common AppData",
275
        "CSIDL_LOCAL_APPDATA": "Local AppData",
276
    }[csidl_name]
277
278
    key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
279
        r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
280
    dir, type = _winreg.QueryValueEx(key, shell_folder_name)
281
    return dir
282
283
def _get_win_folder_with_pywin32(csidl_name):
284
    from win32com.shell import shellcon, shell
285
    dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
286
    # Try to make this a unicode path because SHGetFolderPath does
287
    # not return unicode strings when there is unicode data in the
288
    # path.
289
    try:
290
        dir = unicode(dir)
291
292
        # Downgrade to short path name if have highbit chars. See
293
        # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
294
        has_high_char = False
295
        for c in dir:
296
            if ord(c) > 255:
297
                has_high_char = True
298
                break
299
        if has_high_char:
300
            try:
301
                import win32api
302
                dir = win32api.GetShortPathName(dir)
303
            except ImportError:
304
                pass
305
    except UnicodeError:
306
        pass
307
    return dir
308
309
def _get_win_folder_with_ctypes(csidl_name):
310
    import ctypes
311
312
    csidl_const = {
313
        "CSIDL_APPDATA": 26,
314
        "CSIDL_COMMON_APPDATA": 35,
315
        "CSIDL_LOCAL_APPDATA": 28,
316
    }[csidl_name]
317
318
    buf = ctypes.create_unicode_buffer(1024)
319
    ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
320
321
    # Downgrade to short path name if have highbit chars. See
322
    # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
323
    has_high_char = False
324
    for c in buf:
325
        if ord(c) > 255:
326
            has_high_char = True
327
            break
328
    if has_high_char:
329
        buf2 = ctypes.create_unicode_buffer(1024)
330
        if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
331
            buf = buf2
332
333
    return buf.value
334
335
if sys.platform == "win32":
336
    try:
337
        import win32com.shell
338
        _get_win_folder = _get_win_folder_with_pywin32
339
    except ImportError:
340
        try:
341
            import ctypes
342
            _get_win_folder = _get_win_folder_with_ctypes
343
        except ImportError:
344
            _get_win_folder = _get_win_folder_from_registry
345
346
347
348
#---- self test code
349
350
if __name__ == "__main__":
351
    appname = "MyApp"
352
    appauthor = "MyCompany"
353
354
    props = ("user_data_dir", "site_data_dir", "user_cache_dir",
355
        "user_log_dir")
356
357
    print("-- app dirs (without optional 'version')")
358
    dirs = AppDirs(appname, appauthor, version="1.0")
359
    for prop in props:
360
        print("%s: %s" % (prop, getattr(dirs, prop)))
361
362
    print("\n-- app dirs (with optional 'version')")
363
    dirs = AppDirs(appname, appauthor)
364
    for prop in props:
365
        print("%s: %s" % (prop, getattr(dirs, prop)))
366
367
368
369