/tsgettoolbox/appdirs.py

https://bitbucket.org/tmilliman/tsgettoolbox · Python · 588 lines · 505 code · 34 blank · 49 comment · 72 complexity · 0f5804ed37f3b503840730016705c2b0 MD5 · raw file

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