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, 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'): # "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 """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 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 data 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 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 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_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 Mac OS X: ~/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