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# - Mac OS X: 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, 3) 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'): # "Mac OS X", 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 Mac OS X: ~/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 r"""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 site data directories are: 121 Mac OS X: /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 config directories are: 188 Mac OS X: 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 default "~/.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 r"""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 site config directories are: 226 Mac OS X: 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 Mac OS X: ~/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_state_dir(appname=None, appauthor=None, version=None, roaming=False): 315 r"""Return full path to the user-specific state 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 "roaming" (boolean, default False) can be set True to use the Windows 329 roaming appdata directory. That means that for users on a Windows 330 network setup for roaming profiles, this user data will be 331 sync'd on login. See 332 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> 333 for a discussion of issues. 334 335 Typical user state directories are: 336 Mac OS X: same as user_data_dir 337 Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined 338 Win *: same as user_data_dir 339 340 For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state> 341 to extend the XDG spec and support $XDG_STATE_HOME. 342 343 That means, by default "~/.local/state/<AppName>". 344 """ 345 if system in ["win32", "darwin"]: 346 path = user_data_dir(appname, appauthor, None, roaming) 347 else: 348 path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) 349 if appname: 350 path = os.path.join(path, appname) 351 if appname and version: 352 path = os.path.join(path, version) 353 return path 354 355 356def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): 357 r"""Return full path to the user-specific log dir for this application. 358 359 "appname" is the name of application. 360 If None, just the system directory is returned. 361 "appauthor" (only used on Windows) is the name of the 362 appauthor or distributing body for this application. Typically 363 it is the owning company name. This falls back to appname. You may 364 pass False to disable it. 365 "version" is an optional version path element to append to the 366 path. You might want to use this if you want multiple versions 367 of your app to be able to run independently. If used, this 368 would typically be "<major>.<minor>". 369 Only applied when appname is present. 370 "opinion" (boolean) can be False to disable the appending of 371 "Logs" to the base app data dir for Windows, and "log" to the 372 base cache dir for Unix. See discussion below. 373 374 Typical user log directories are: 375 Mac OS X: ~/Library/Logs/<AppName> 376 Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined 377 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs 378 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs 379 380 On Windows the only suggestion in the MSDN docs is that local settings 381 go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in 382 examples of what some windows apps use for a logs dir.) 383 384 OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` 385 value for Windows and appends "log" to the user cache dir for Unix. 386 This can be disabled with the `opinion=False` option. 387 """ 388 if system == "darwin": 389 path = os.path.join( 390 os.path.expanduser('~/Library/Logs'), 391 appname) 392 elif system == "win32": 393 path = user_data_dir(appname, appauthor, version) 394 version = False 395 if opinion: 396 path = os.path.join(path, "Logs") 397 else: 398 path = user_cache_dir(appname, appauthor, version) 399 version = False 400 if opinion: 401 path = os.path.join(path, "log") 402 if appname and version: 403 path = os.path.join(path, version) 404 return path 405 406 407class AppDirs(object): 408 """Convenience wrapper for getting application dirs.""" 409 def __init__(self, appname=None, appauthor=None, version=None, 410 roaming=False, multipath=False): 411 self.appname = appname 412 self.appauthor = appauthor 413 self.version = version 414 self.roaming = roaming 415 self.multipath = multipath 416 417 @property 418 def user_data_dir(self): 419 return user_data_dir(self.appname, self.appauthor, 420 version=self.version, roaming=self.roaming) 421 422 @property 423 def site_data_dir(self): 424 return site_data_dir(self.appname, self.appauthor, 425 version=self.version, multipath=self.multipath) 426 427 @property 428 def user_config_dir(self): 429 return user_config_dir(self.appname, self.appauthor, 430 version=self.version, roaming=self.roaming) 431 432 @property 433 def site_config_dir(self): 434 return site_config_dir(self.appname, self.appauthor, 435 version=self.version, multipath=self.multipath) 436 437 @property 438 def user_cache_dir(self): 439 return user_cache_dir(self.appname, self.appauthor, 440 version=self.version) 441 442 @property 443 def user_state_dir(self): 444 return user_state_dir(self.appname, self.appauthor, 445 version=self.version) 446 447 @property 448 def user_log_dir(self): 449 return user_log_dir(self.appname, self.appauthor, 450 version=self.version) 451 452 453#---- internal support stuff 454 455def _get_win_folder_from_registry(csidl_name): 456 """This is a fallback technique at best. I'm not sure if using the 457 registry for this guarantees us the correct answer for all CSIDL_* 458 names. 459 """ 460 if PY3: 461 import winreg as _winreg 462 else: 463 import _winreg 464 465 shell_folder_name = { 466 "CSIDL_APPDATA": "AppData", 467 "CSIDL_COMMON_APPDATA": "Common AppData", 468 "CSIDL_LOCAL_APPDATA": "Local AppData", 469 }[csidl_name] 470 471 key = _winreg.OpenKey( 472 _winreg.HKEY_CURRENT_USER, 473 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" 474 ) 475 dir, type = _winreg.QueryValueEx(key, shell_folder_name) 476 return dir 477 478 479def _get_win_folder_with_pywin32(csidl_name): 480 from win32com.shell import shellcon, shell 481 dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) 482 # Try to make this a unicode path because SHGetFolderPath does 483 # not return unicode strings when there is unicode data in the 484 # path. 485 try: 486 dir = unicode(dir) 487 488 # Downgrade to short path name if have highbit chars. See 489 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 490 has_high_char = False 491 for c in dir: 492 if ord(c) > 255: 493 has_high_char = True 494 break 495 if has_high_char: 496 try: 497 import win32api 498 dir = win32api.GetShortPathName(dir) 499 except ImportError: 500 pass 501 except UnicodeError: 502 pass 503 return dir 504 505 506def _get_win_folder_with_ctypes(csidl_name): 507 import ctypes 508 509 csidl_const = { 510 "CSIDL_APPDATA": 26, 511 "CSIDL_COMMON_APPDATA": 35, 512 "CSIDL_LOCAL_APPDATA": 28, 513 }[csidl_name] 514 515 buf = ctypes.create_unicode_buffer(1024) 516 ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) 517 518 # Downgrade to short path name if have highbit chars. See 519 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 520 has_high_char = False 521 for c in buf: 522 if ord(c) > 255: 523 has_high_char = True 524 break 525 if has_high_char: 526 buf2 = ctypes.create_unicode_buffer(1024) 527 if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): 528 buf = buf2 529 530 return buf.value 531 532def _get_win_folder_with_jna(csidl_name): 533 import array 534 from com.sun import jna 535 from com.sun.jna.platform import win32 536 537 buf_size = win32.WinDef.MAX_PATH * 2 538 buf = array.zeros('c', buf_size) 539 shell = win32.Shell32.INSTANCE 540 shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) 541 dir = jna.Native.toString(buf.tostring()).rstrip("\0") 542 543 # Downgrade to short path name if have highbit chars. See 544 # <http://bugs.activestate.com/show_bug.cgi?id=85099>. 545 has_high_char = False 546 for c in dir: 547 if ord(c) > 255: 548 has_high_char = True 549 break 550 if has_high_char: 551 buf = array.zeros('c', buf_size) 552 kernel = win32.Kernel32.INSTANCE 553 if kernel.GetShortPathName(dir, buf, buf_size): 554 dir = jna.Native.toString(buf.tostring()).rstrip("\0") 555 556 return dir 557 558if system == "win32": 559 try: 560 import win32com.shell 561 _get_win_folder = _get_win_folder_with_pywin32 562 except ImportError: 563 try: 564 from ctypes import windll 565 _get_win_folder = _get_win_folder_with_ctypes 566 except ImportError: 567 try: 568 import com.sun.jna 569 _get_win_folder = _get_win_folder_with_jna 570 except ImportError: 571 _get_win_folder = _get_win_folder_from_registry 572 573 574#---- self test code 575 576if __name__ == "__main__": 577 appname = "MyApp" 578 appauthor = "MyCompany" 579 580 props = ("user_data_dir", 581 "user_config_dir", 582 "user_cache_dir", 583 "user_state_dir", 584 "user_log_dir", 585 "site_data_dir", 586 "site_config_dir") 587 588 print("-- app dirs %s --" % __version__) 589 590 print("-- app dirs (with optional 'version')") 591 dirs = AppDirs(appname, appauthor, version="1.0") 592 for prop in props: 593 print("%s: %s" % (prop, getattr(dirs, prop))) 594 595 print("\n-- app dirs (without optional 'version')") 596 dirs = AppDirs(appname, appauthor) 597 for prop in props: 598 print("%s: %s" % (prop, getattr(dirs, prop))) 599 600 print("\n-- app dirs (without optional 'appauthor')") 601 dirs = AppDirs(appname) 602 for prop in props: 603 print("%s: %s" % (prop, getattr(dirs, prop))) 604 605 print("\n-- app dirs (with disabled 'appauthor')") 606 dirs = AppDirs(appname, appauthor=False) 607 for prop in props: 608 print("%s: %s" % (prop, getattr(dirs, prop))) 609