1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2005-2010 ActiveState Software Inc.
4# Copyright (c) 2013 Eddy Petrișor
5
6"""Utilities for determining application-specific dirs.
7
8See <http://github.com/ActiveState/appdirs> for details and usage.
9"""
10from __future__ import print_function
11# Dev Notes:
12# - MSDN on where to store app data files:
13#   http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
14# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
15# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
16
17__version_info__ = (1, 3, 0)
18__version__ = '.'.join(str(v) for v in __version_info__)
19
20
21import sys
22import os
23
24
25def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
26    r"""Return full path to the user-specific data dir for this application.
27
28        "appname" is the name of application.
29            If None, just the system directory is returned.
30        "appauthor" (only required and used on Windows) is the name of the
31            appauthor or distributing body for this application. Typically
32            it is the owning company name. This falls back to appname.
33        "version" is an optional version path element to append to the
34            path. You might want to use this if you want multiple versions
35            of your app to be able to run independently. If used, this
36            would typically be "<major>.<minor>".
37            Only applied when appname is present.
38        "roaming" (boolean, default False) can be set True to use the Windows
39            roaming appdata directory. That means that for users on a Windows
40            network setup for roaming profiles, this user data will be
41            sync'd on login. See
42            <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
43            for a discussion of issues.
44
45    Typical user data directories are:
46        Mac OS X:               ~/Library/Application Support/<AppName>
47        Unix:                   ~/.local/share/<AppName>    # or in $XDG_DATA_HOME, if defined
48        Win XP (not roaming):   C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
49        Win XP (roaming):       C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
50        Win 7  (not roaming):   C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
51        Win 7  (roaming):       C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
52
53    For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
54    That means, by default "~/.local/share/<AppName>".
55    """
56    if sys.platform == "win32":
57        if appauthor is None:
58            appauthor = appname
59        const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
60        path = os.path.normpath(_get_win_folder(const))
61        if appname:
62            path = os.path.join(path, appauthor, appname)
63    elif sys.platform == 'darwin':
64        path = os.path.expanduser('~/Library/Application Support/')
65        if appname:
66            path = os.path.join(path, appname)
67    else:
68        path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
69        if appname:
70            path = os.path.join(path, appname)
71    if appname and version:
72        path = os.path.join(path, version)
73    return path
74
75
76def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
77    """Return full path to the user-shared data dir for this application.
78
79        "appname" is the name of application.
80            If None, just the system directory is returned.
81        "appauthor" (only required and used on Windows) is the name of the
82            appauthor or distributing body for this application. Typically
83            it is the owning company name. This falls back to appname.
84        "version" is an optional version path element to append to the
85            path. You might want to use this if you want multiple versions
86            of your app to be able to run independently. If used, this
87            would typically be "<major>.<minor>".
88            Only applied when appname is present.
89        "multipath" is an optional parameter only applicable to *nix
90            which indicates that the entire list of data dirs should be
91            returned. By default, the first item from XDG_DATA_DIRS is
92            returned, or '/usr/local/share/<AppName>',
93            if XDG_DATA_DIRS is not set
94
95    Typical user data directories are:
96        Mac OS X:   /Library/Application Support/<AppName>
97        Unix:       /usr/local/share/<AppName> or /usr/share/<AppName>
98        Win XP:     C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
99        Vista:      (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
100        Win 7:      C:\ProgramData\<AppAuthor>\<AppName>   # Hidden, but writeable on Win 7.
101
102    For Unix, this is using the $XDG_DATA_DIRS[0] default.
103
104    WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
105    """
106    if sys.platform == "win32":
107        if appauthor is None:
108            appauthor = appname
109        path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
110        if appname:
111            path = os.path.join(path, appauthor, appname)
112    elif sys.platform == 'darwin':
113        path = os.path.expanduser('/Library/Application Support')
114        if appname:
115            path = os.path.join(path, appname)
116    else:
117        # XDG default for $XDG_DATA_DIRS
118        # only first, if multipath is False
119        path = os.getenv('XDG_DATA_DIRS',
120                        os.pathsep.join(['/usr/local/share', '/usr/share']))
121        pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ]
122        if appname:
123            if version:
124                appname = os.path.join(appname, version)
125            pathlist = [ os.sep.join([x, appname]) for x in pathlist ]
126
127        if multipath:
128            path = os.pathsep.join(pathlist)
129        else:
130            path = pathlist[0]
131        return path
132
133    if appname and version:
134        path = os.path.join(path, version)
135    return path
136
137
138def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
139    r"""Return full path to the user-specific config dir for this application.
140
141        "appname" is the name of application.
142            If None, just the system directory is returned.
143        "appauthor" (only required and used on Windows) is the name of the
144            appauthor or distributing body for this application. Typically
145            it is the owning company name. This falls back to appname.
146        "version" is an optional version path element to append to the
147            path. You might want to use this if you want multiple versions
148            of your app to be able to run independently. If used, this
149            would typically be "<major>.<minor>".
150            Only applied when appname is present.
151        "roaming" (boolean, default False) can be set True to use the Windows
152            roaming appdata directory. That means that for users on a Windows
153            network setup for roaming profiles, this user data will be
154            sync'd on login. See
155            <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
156            for a discussion of issues.
157
158    Typical user data directories are:
159        Mac OS X:               same as user_data_dir
160        Unix:                   ~/.config/<AppName>     # or in $XDG_CONFIG_HOME, if defined
161        Win *:                  same as user_data_dir
162
163    For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
164    That means, by default "~/.local/share/<AppName>".
165    """
166    if sys.platform in [ "win32", "darwin" ]:
167        path = user_data_dir(appname, appauthor, None, roaming)
168    else:
169        path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
170        if appname:
171            path = os.path.join(path, appname)
172    if appname and version:
173        path = os.path.join(path, version)
174    return path
175
176
177def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
178    """Return full path to the user-shared data dir for this application.
179
180        "appname" is the name of application.
181            If None, just the system directory is returned.
182        "appauthor" (only required and used on Windows) is the name of the
183            appauthor or distributing body for this application. Typically
184            it is the owning company name. This falls back to appname.
185        "version" is an optional version path element to append to the
186            path. You might want to use this if you want multiple versions
187            of your app to be able to run independently. If used, this
188            would typically be "<major>.<minor>".
189            Only applied when appname is present.
190        "multipath" is an optional parameter only applicable to *nix
191            which indicates that the entire list of config dirs should be
192            returned. By default, the first item from XDG_CONFIG_DIRS is
193            returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
194
195    Typical user data directories are:
196        Mac OS X:   same as site_data_dir
197        Unix:       /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
198                    $XDG_CONFIG_DIRS
199        Win *:      same as site_data_dir
200        Vista:      (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
201
202    For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
203
204    WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
205    """
206    if sys.platform in [ "win32", "darwin" ]:
207        path = site_data_dir(appname, appauthor)
208        if appname and version:
209            path = os.path.join(path, version)
210    else:
211        # XDG default for $XDG_CONFIG_DIRS
212        # only first, if multipath is False
213        path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
214        pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ]
215        if appname:
216            if version:
217                appname = os.path.join(appname, version)
218            pathlist = [ os.sep.join([x, appname]) for x in pathlist ]
219
220        if multipath:
221            path = os.pathsep.join(pathlist)
222        else:
223            path = pathlist[0]
224    return path
225
226def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
227    r"""Return full path to the user-specific cache dir for this application.
228
229        "appname" is the name of application.
230            If None, just the system directory is returned.
231        "appauthor" (only required and used on Windows) is the name of the
232            appauthor or distributing body for this application. Typically
233            it is the owning company name. This falls back to appname.
234        "version" is an optional version path element to append to the
235            path. You might want to use this if you want multiple versions
236            of your app to be able to run independently. If used, this
237            would typically be "<major>.<minor>".
238            Only applied when appname is present.
239        "opinion" (boolean) can be False to disable the appending of
240            "Cache" to the base app data dir for Windows. See
241            discussion below.
242
243    Typical user cache directories are:
244        Mac OS X:   ~/Library/Caches/<AppName>
245        Unix:       ~/.cache/<AppName> (XDG default)
246        Win XP:     C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
247        Vista:      C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
248
249    On Windows the only suggestion in the MSDN docs is that local settings go in
250    the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
251    app data dir (the default returned by `user_data_dir` above). Apps typically
252    put cache data somewhere *under* the given dir here. Some examples:
253        ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
254        ...\Acme\SuperApp\Cache\1.0
255    OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
256    This can be disabled with the `opinion=False` option.
257    """
258    if sys.platform == "win32":
259        if appauthor is None:
260            appauthor = appname
261        path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
262        if appname:
263            path = os.path.join(path, appauthor, appname)
264            if opinion:
265                path = os.path.join(path, "Cache")
266    elif sys.platform == 'darwin':
267        path = os.path.expanduser('~/Library/Caches')
268        if appname:
269            path = os.path.join(path, appname)
270    else:
271        path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
272        if appname:
273            path = os.path.join(path, appname)
274    if appname and version:
275        path = os.path.join(path, version)
276    return path
277
278def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
279    r"""Return full path to the user-specific log dir for this application.
280
281        "appname" is the name of application.
282            If None, just the system directory is returned.
283        "appauthor" (only required and used on Windows) is the name of the
284            appauthor or distributing body for this application. Typically
285            it is the owning company name. This falls back to appname.
286        "version" is an optional version path element to append to the
287            path. You might want to use this if you want multiple versions
288            of your app to be able to run independently. If used, this
289            would typically be "<major>.<minor>".
290            Only applied when appname is present.
291        "opinion" (boolean) can be False to disable the appending of
292            "Logs" to the base app data dir for Windows, and "log" to the
293            base cache dir for Unix. See discussion below.
294
295    Typical user cache directories are:
296        Mac OS X:   ~/Library/Logs/<AppName>
297        Unix:       ~/.cache/<AppName>/log  # or under $XDG_CACHE_HOME if defined
298        Win XP:     C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
299        Vista:      C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
300
301    On Windows the only suggestion in the MSDN docs is that local settings
302    go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
303    examples of what some windows apps use for a logs dir.)
304
305    OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
306    value for Windows and appends "log" to the user cache dir for Unix.
307    This can be disabled with the `opinion=False` option.
308    """
309    if sys.platform == "darwin":
310        path = os.path.join(
311            os.path.expanduser('~/Library/Logs'),
312            appname)
313    elif sys.platform == "win32":
314        path = user_data_dir(appname, appauthor, version); version=False
315        if opinion:
316            path = os.path.join(path, "Logs")
317    else:
318        path = user_cache_dir(appname, appauthor, version); version=False
319        if opinion:
320            path = os.path.join(path, "log")
321    if appname and version:
322        path = os.path.join(path, version)
323    return path
324
325
326class AppDirs(object):
327    """Convenience wrapper for getting application dirs."""
328    def __init__(self, appname, appauthor=None, version=None,
329                    roaming=False, multipath=False):
330        self.appname = appname
331        self.appauthor = appauthor
332        self.version = version
333        self.roaming = roaming
334        self.multipath = multipath
335    @property
336    def user_data_dir(self):
337        return user_data_dir(self.appname, self.appauthor,
338            version=self.version, roaming=self.roaming)
339    @property
340    def site_data_dir(self):
341        return site_data_dir(self.appname, self.appauthor,
342            version=self.version, multipath=self.multipath)
343    @property
344    def user_config_dir(self):
345        return user_config_dir(self.appname, self.appauthor,
346            version=self.version, roaming=self.roaming)
347    @property
348    def site_config_dir(self):
349        return site_data_dir(self.appname, self.appauthor,
350            version=self.version, multipath=self.multipath)
351    @property
352    def user_cache_dir(self):
353        return user_cache_dir(self.appname, self.appauthor,
354            version=self.version)
355    @property
356    def user_log_dir(self):
357        return user_log_dir(self.appname, self.appauthor,
358            version=self.version)
359
360
361
362
363#---- internal support stuff
364
365def _get_win_folder_from_registry(csidl_name):
366    """This is a fallback technique at best. I'm not sure if using the
367    registry for this guarantees us the correct answer for all CSIDL_*
368    names.
369    """
370    import winreg as _winreg
371
372    shell_folder_name = {
373        "CSIDL_APPDATA": "AppData",
374        "CSIDL_COMMON_APPDATA": "Common AppData",
375        "CSIDL_LOCAL_APPDATA": "Local AppData",
376    }[csidl_name]
377
378    key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
379        r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
380    dir, type = _winreg.QueryValueEx(key, shell_folder_name)
381    return dir
382
383def _get_win_folder_with_pywin32(csidl_name):
384    from win32com.shell import shellcon, shell
385    dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
386    # Try to make this a unicode path because SHGetFolderPath does
387    # not return unicode strings when there is unicode data in the
388    # path.
389    try:
390        dir = str(dir)
391
392        # Downgrade to short path name if have highbit chars. See
393        # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
394        has_high_char = False
395        for c in dir:
396            if ord(c) > 255:
397                has_high_char = True
398                break
399        if has_high_char:
400            try:
401                import win32api
402                dir = win32api.GetShortPathName(dir)
403            except ImportError:
404                pass
405    except UnicodeError:
406        pass
407    return dir
408
409def _get_win_folder_with_ctypes(csidl_name):
410    import ctypes
411
412    csidl_const = {
413        "CSIDL_APPDATA": 26,
414        "CSIDL_COMMON_APPDATA": 35,
415        "CSIDL_LOCAL_APPDATA": 28,
416    }[csidl_name]
417
418    buf = ctypes.create_unicode_buffer(1024)
419    ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
420
421    # Downgrade to short path name if have highbit chars. See
422    # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
423    has_high_char = False
424    for c in buf:
425        if ord(c) > 255:
426            has_high_char = True
427            break
428    if has_high_char:
429        buf2 = ctypes.create_unicode_buffer(1024)
430        if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
431            buf = buf2
432
433    return buf.value
434
435if sys.platform == "win32":
436    try:
437        import win32com.shell
438        _get_win_folder = _get_win_folder_with_pywin32
439    except ImportError:
440        try:
441            import ctypes
442            _get_win_folder = _get_win_folder_with_ctypes
443        except ImportError:
444            _get_win_folder = _get_win_folder_from_registry
445
446
447
448#---- self test code
449
450if __name__ == "__main__":
451    appname = "MyApp"
452    appauthor = "MyCompany"
453
454    props = ("user_data_dir", "site_data_dir",
455             "user_config_dir", "site_config_dir",
456             "user_cache_dir", "user_log_dir")
457
458    print("-- app dirs (with optional 'version')")
459    dirs = AppDirs(appname, appauthor, version="1.0")
460    for prop in props:
461        print("%s: %s" % (prop, getattr(dirs, prop)))
462
463    print("\n-- app dirs (without optional 'version')")
464    dirs = AppDirs(appname, appauthor)
465    for prop in props:
466        print("%s: %s" % (prop, getattr(dirs, prop)))
467
468    print("\n-- app dirs (without optional 'appauthor')")
469    dirs = AppDirs(appname)
470    for prop in props:
471        print("%s: %s" % (prop, getattr(dirs, prop)))
472