You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

604 lines
24 KiB

4 years ago
  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, 3)
  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. r"""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 site 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 config 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 default "~/.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. r"""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 site config 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_state_dir(appname=None, appauthor=None, version=None, roaming=False):
  274. r"""Return full path to the user-specific state 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. "roaming" (boolean, default False) can be set True to use the Windows
  287. roaming appdata directory. That means that for users on a Windows
  288. network setup for roaming profiles, this user data will be
  289. sync'd on login. See
  290. <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
  291. for a discussion of issues.
  292. Typical user state directories are:
  293. Mac OS X: same as user_data_dir
  294. Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined
  295. Win *: same as user_data_dir
  296. For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state>
  297. to extend the XDG spec and support $XDG_STATE_HOME.
  298. That means, by default "~/.local/state/<AppName>".
  299. """
  300. if system in ["win32", "darwin"]:
  301. path = user_data_dir(appname, appauthor, None, roaming)
  302. else:
  303. path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
  304. if appname:
  305. path = os.path.join(path, appname)
  306. if appname and version:
  307. path = os.path.join(path, version)
  308. return path
  309. def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
  310. r"""Return full path to the user-specific log dir for this application.
  311. "appname" is the name of application.
  312. If None, just the system directory is returned.
  313. "appauthor" (only used on Windows) is the name of the
  314. appauthor or distributing body for this application. Typically
  315. it is the owning company name. This falls back to appname. You may
  316. pass False to disable it.
  317. "version" is an optional version path element to append to the
  318. path. You might want to use this if you want multiple versions
  319. of your app to be able to run independently. If used, this
  320. would typically be "<major>.<minor>".
  321. Only applied when appname is present.
  322. "opinion" (boolean) can be False to disable the appending of
  323. "Logs" to the base app data dir for Windows, and "log" to the
  324. base cache dir for Unix. See discussion below.
  325. Typical user log directories are:
  326. Mac OS X: ~/Library/Logs/<AppName>
  327. Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
  328. Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
  329. Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
  330. On Windows the only suggestion in the MSDN docs is that local settings
  331. go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
  332. examples of what some windows apps use for a logs dir.)
  333. OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
  334. value for Windows and appends "log" to the user cache dir for Unix.
  335. This can be disabled with the `opinion=False` option.
  336. """
  337. if system == "darwin":
  338. path = os.path.join(
  339. os.path.expanduser('~/Library/Logs'),
  340. appname)
  341. elif system == "win32":
  342. path = user_data_dir(appname, appauthor, version)
  343. version = False
  344. if opinion:
  345. path = os.path.join(path, "Logs")
  346. else:
  347. path = user_cache_dir(appname, appauthor, version)
  348. version = False
  349. if opinion:
  350. path = os.path.join(path, "log")
  351. if appname and version:
  352. path = os.path.join(path, version)
  353. return path
  354. class AppDirs(object):
  355. """Convenience wrapper for getting application dirs."""
  356. def __init__(self, appname=None, appauthor=None, version=None,
  357. roaming=False, multipath=False):
  358. self.appname = appname
  359. self.appauthor = appauthor
  360. self.version = version
  361. self.roaming = roaming
  362. self.multipath = multipath
  363. @property
  364. def user_data_dir(self):
  365. return user_data_dir(self.appname, self.appauthor,
  366. version=self.version, roaming=self.roaming)
  367. @property
  368. def site_data_dir(self):
  369. return site_data_dir(self.appname, self.appauthor,
  370. version=self.version, multipath=self.multipath)
  371. @property
  372. def user_config_dir(self):
  373. return user_config_dir(self.appname, self.appauthor,
  374. version=self.version, roaming=self.roaming)
  375. @property
  376. def site_config_dir(self):
  377. return site_config_dir(self.appname, self.appauthor,
  378. version=self.version, multipath=self.multipath)
  379. @property
  380. def user_cache_dir(self):
  381. return user_cache_dir(self.appname, self.appauthor,
  382. version=self.version)
  383. @property
  384. def user_state_dir(self):
  385. return user_state_dir(self.appname, self.appauthor,
  386. version=self.version)
  387. @property
  388. def user_log_dir(self):
  389. return user_log_dir(self.appname, self.appauthor,
  390. version=self.version)
  391. #---- internal support stuff
  392. def _get_win_folder_from_registry(csidl_name):
  393. """This is a fallback technique at best. I'm not sure if using the
  394. registry for this guarantees us the correct answer for all CSIDL_*
  395. names.
  396. """
  397. if PY3:
  398. import winreg as _winreg
  399. else:
  400. import _winreg
  401. shell_folder_name = {
  402. "CSIDL_APPDATA": "AppData",
  403. "CSIDL_COMMON_APPDATA": "Common AppData",
  404. "CSIDL_LOCAL_APPDATA": "Local AppData",
  405. }[csidl_name]
  406. key = _winreg.OpenKey(
  407. _winreg.HKEY_CURRENT_USER,
  408. r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
  409. )
  410. dir, type = _winreg.QueryValueEx(key, shell_folder_name)
  411. return dir
  412. def _get_win_folder_with_pywin32(csidl_name):
  413. from win32com.shell import shellcon, shell
  414. dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
  415. # Try to make this a unicode path because SHGetFolderPath does
  416. # not return unicode strings when there is unicode data in the
  417. # path.
  418. try:
  419. dir = unicode(dir)
  420. # Downgrade to short path name if have highbit chars. See
  421. # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
  422. has_high_char = False
  423. for c in dir:
  424. if ord(c) > 255:
  425. has_high_char = True
  426. break
  427. if has_high_char:
  428. try:
  429. import win32api
  430. dir = win32api.GetShortPathName(dir)
  431. except ImportError:
  432. pass
  433. except UnicodeError:
  434. pass
  435. return dir
  436. def _get_win_folder_with_ctypes(csidl_name):
  437. import ctypes
  438. csidl_const = {
  439. "CSIDL_APPDATA": 26,
  440. "CSIDL_COMMON_APPDATA": 35,
  441. "CSIDL_LOCAL_APPDATA": 28,
  442. }[csidl_name]
  443. buf = ctypes.create_unicode_buffer(1024)
  444. ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
  445. # Downgrade to short path name if have highbit chars. See
  446. # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
  447. has_high_char = False
  448. for c in buf:
  449. if ord(c) > 255:
  450. has_high_char = True
  451. break
  452. if has_high_char:
  453. buf2 = ctypes.create_unicode_buffer(1024)
  454. if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
  455. buf = buf2
  456. return buf.value
  457. def _get_win_folder_with_jna(csidl_name):
  458. import array
  459. from com.sun import jna
  460. from com.sun.jna.platform import win32
  461. buf_size = win32.WinDef.MAX_PATH * 2
  462. buf = array.zeros('c', buf_size)
  463. shell = win32.Shell32.INSTANCE
  464. shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
  465. dir = jna.Native.toString(buf.tostring()).rstrip("\0")
  466. # Downgrade to short path name if have highbit chars. See
  467. # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
  468. has_high_char = False
  469. for c in dir:
  470. if ord(c) > 255:
  471. has_high_char = True
  472. break
  473. if has_high_char:
  474. buf = array.zeros('c', buf_size)
  475. kernel = win32.Kernel32.INSTANCE
  476. if kernel.GetShortPathName(dir, buf, buf_size):
  477. dir = jna.Native.toString(buf.tostring()).rstrip("\0")
  478. return dir
  479. if system == "win32":
  480. try:
  481. from ctypes import windll
  482. _get_win_folder = _get_win_folder_with_ctypes
  483. except ImportError:
  484. try:
  485. import com.sun.jna
  486. _get_win_folder = _get_win_folder_with_jna
  487. except ImportError:
  488. _get_win_folder = _get_win_folder_from_registry
  489. #---- self test code
  490. if __name__ == "__main__":
  491. appname = "MyApp"
  492. appauthor = "MyCompany"
  493. props = ("user_data_dir",
  494. "user_config_dir",
  495. "user_cache_dir",
  496. "user_state_dir",
  497. "user_log_dir",
  498. "site_data_dir",
  499. "site_config_dir")
  500. print("-- app dirs %s --" % __version__)
  501. print("-- app dirs (with optional 'version')")
  502. dirs = AppDirs(appname, appauthor, version="1.0")
  503. for prop in props:
  504. print("%s: %s" % (prop, getattr(dirs, prop)))
  505. print("\n-- app dirs (without optional 'version')")
  506. dirs = AppDirs(appname, appauthor)
  507. for prop in props:
  508. print("%s: %s" % (prop, getattr(dirs, prop)))
  509. print("\n-- app dirs (without optional 'appauthor')")
  510. dirs = AppDirs(appname)
  511. for prop in props:
  512. print("%s: %s" % (prop, getattr(dirs, prop)))
  513. print("\n-- app dirs (with disabled 'appauthor')")
  514. dirs = AppDirs(appname, appauthor=False)
  515. for prop in props:
  516. print("%s: %s" % (prop, getattr(dirs, prop)))