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