appdirs.py 22 KB

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