blob: 46d4c599f2a672272eaf87a438206c9d8c612dda [file] [log] [blame]
Sam Estep2e269762021-05-14 08:21:46 -07001#!/usr/bin/env python3
SsnL13013842020-04-30 11:27:13 -07002# -*- coding: utf-8 -*-
3# Copyright (c) 2005-2010 ActiveState Software Inc.
4# Copyright (c) 2013 Eddy Petrișor
5
6# flake8: noqa
7
8"""
9This file is directly from
10https://github.com/ActiveState/appdirs/blob/3fe6a83776843a46f20c2e5587afcffe05e03b39/appdirs.py
11
12The license of https://github.com/ActiveState/appdirs copied below:
13
14
15# This is the MIT license
16
17Copyright (c) 2010 ActiveState Software Inc.
18
19Permission is hereby granted, free of charge, to any person obtaining a
20copy of this software and associated documentation files (the
21"Software"), to deal in the Software without restriction, including
22without limitation the rights to use, copy, modify, merge, publish,
23distribute, sublicense, and/or sell copies of the Software, and to
24permit persons to whom the Software is furnished to do so, subject to
25the following conditions:
26
27The above copyright notice and this permission notice shall be included
28in all copies or substantial portions of the Software.
29
30THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
31OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37"""
38
39"""Utilities for determining application-specific dirs.
40
41See <https://github.com/ActiveState/appdirs> for details and usage.
42"""
43# Dev Notes:
44# - MSDN on where to store app data files:
45# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
46# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
47# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
48
49__version__ = "1.4.4"
50__version_info__ = tuple(int(segment) for segment in __version__.split("."))
51
52
Shen Li10224432021-08-12 11:39:31 -070053import os
Huy Do12cb2652022-07-22 02:19:50 +000054import sys
SsnL13013842020-04-30 11:27:13 -070055
Nikita Shulgac6b69a42020-06-15 08:14:54 -070056unicode = str
SsnL13013842020-04-30 11:27:13 -070057
Huy Do12cb2652022-07-22 02:19:50 +000058if sys.platform.startswith("java"):
SsnL13013842020-04-30 11:27:13 -070059 import platform
Huy Do12cb2652022-07-22 02:19:50 +000060
SsnL13013842020-04-30 11:27:13 -070061 os_name = platform.java_ver()[3][0]
Huy Do12cb2652022-07-22 02:19:50 +000062 if os_name.startswith("Windows"): # "Windows XP", "Windows 7", etc.
63 system = "win32"
64 elif os_name.startswith("Mac"): # "Mac OS X", etc.
65 system = "darwin"
66 else: # "Linux", "SunOS", "FreeBSD", etc.
SsnL13013842020-04-30 11:27:13 -070067 # Setting this to "linux2" is not ideal, but only Windows or Mac
68 # are actually checked for and the rest of the module expects
69 # *sys.platform* style strings.
Huy Do12cb2652022-07-22 02:19:50 +000070 system = "linux2"
SsnL13013842020-04-30 11:27:13 -070071else:
72 system = sys.platform
73
74
SsnL13013842020-04-30 11:27:13 -070075def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
76 r"""Return full path to the user-specific data dir for this application.
77
78 "appname" is the name of application.
79 If None, just the system directory is returned.
80 "appauthor" (only used on Windows) is the name of the
81 appauthor or distributing body for this application. Typically
82 it is the owning company name. This falls back to appname. You may
83 pass False to disable it.
84 "version" is an optional version path element to append to the
85 path. You might want to use this if you want multiple versions
86 of your app to be able to run independently. If used, this
87 would typically be "<major>.<minor>".
88 Only applied when appname is present.
89 "roaming" (boolean, default False) can be set True to use the Windows
90 roaming appdata directory. That means that for users on a Windows
91 network setup for roaming profiles, this user data will be
92 sync'd on login. See
93 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
94 for a discussion of issues.
95
96 Typical user data directories are:
97 Mac OS X: ~/Library/Application Support/<AppName>
98 Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
99 Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
100 Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
101 Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
102 Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
103
104 For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
105 That means, by default "~/.local/share/<AppName>".
106 """
107 if system == "win32":
108 if appauthor is None:
109 appauthor = appname
110 const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
111 path = os.path.normpath(_get_win_folder(const))
112 if appname:
113 if appauthor is not False:
114 path = os.path.join(path, appauthor, appname)
115 else:
116 path = os.path.join(path, appname)
Huy Do12cb2652022-07-22 02:19:50 +0000117 elif system == "darwin":
118 path = os.path.expanduser("~/Library/Application Support/")
SsnL13013842020-04-30 11:27:13 -0700119 if appname:
120 path = os.path.join(path, appname)
121 else:
Huy Do12cb2652022-07-22 02:19:50 +0000122 path = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
SsnL13013842020-04-30 11:27:13 -0700123 if appname:
124 path = os.path.join(path, appname)
125 if appname and version:
126 path = os.path.join(path, version)
127 return path
128
129
130def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
131 r"""Return full path to the user-shared data dir for this application.
132
133 "appname" is the name of application.
134 If None, just the system directory is returned.
135 "appauthor" (only used on Windows) is the name of the
136 appauthor or distributing body for this application. Typically
137 it is the owning company name. This falls back to appname. You may
138 pass False to disable it.
139 "version" is an optional version path element to append to the
140 path. You might want to use this if you want multiple versions
141 of your app to be able to run independently. If used, this
142 would typically be "<major>.<minor>".
143 Only applied when appname is present.
144 "multipath" is an optional parameter only applicable to *nix
145 which indicates that the entire list of data dirs should be
146 returned. By default, the first item from XDG_DATA_DIRS is
147 returned, or '/usr/local/share/<AppName>',
148 if XDG_DATA_DIRS is not set
149
150 Typical site data directories are:
151 Mac OS X: /Library/Application Support/<AppName>
152 Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
153 Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
154 Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
155 Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
156
157 For Unix, this is using the $XDG_DATA_DIRS[0] default.
158
159 WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
160 """
161 if system == "win32":
162 if appauthor is None:
163 appauthor = appname
164 path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
165 if appname:
166 if appauthor is not False:
167 path = os.path.join(path, appauthor, appname)
168 else:
169 path = os.path.join(path, appname)
Huy Do12cb2652022-07-22 02:19:50 +0000170 elif system == "darwin":
171 path = os.path.expanduser("/Library/Application Support")
SsnL13013842020-04-30 11:27:13 -0700172 if appname:
173 path = os.path.join(path, appname)
174 else:
175 # XDG default for $XDG_DATA_DIRS
176 # only first, if multipath is False
Huy Do12cb2652022-07-22 02:19:50 +0000177 path = os.getenv(
178 "XDG_DATA_DIRS", os.pathsep.join(["/usr/local/share", "/usr/share"])
179 )
180 pathlist = [
181 os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)
182 ]
SsnL13013842020-04-30 11:27:13 -0700183 if appname:
184 if version:
185 appname = os.path.join(appname, version)
186 pathlist = [os.sep.join([x, appname]) for x in pathlist]
187
188 if multipath:
189 path = os.pathsep.join(pathlist)
190 else:
191 path = pathlist[0]
192 return path
193
194 if appname and version:
195 path = os.path.join(path, version)
196 return path
197
198
199def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
200 r"""Return full path to the user-specific config dir for this application.
201
202 "appname" is the name of application.
203 If None, just the system directory is returned.
204 "appauthor" (only used on Windows) is the name of the
205 appauthor or distributing body for this application. Typically
206 it is the owning company name. This falls back to appname. You may
207 pass False to disable it.
208 "version" is an optional version path element to append to the
209 path. You might want to use this if you want multiple versions
210 of your app to be able to run independently. If used, this
211 would typically be "<major>.<minor>".
212 Only applied when appname is present.
213 "roaming" (boolean, default False) can be set True to use the Windows
214 roaming appdata directory. That means that for users on a Windows
215 network setup for roaming profiles, this user data will be
216 sync'd on login. See
217 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
218 for a discussion of issues.
219
220 Typical user config directories are:
221 Mac OS X: ~/Library/Preferences/<AppName>
222 Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
223 Win *: same as user_data_dir
224
225 For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
226 That means, by default "~/.config/<AppName>".
227 """
228 if system == "win32":
229 path = user_data_dir(appname, appauthor, None, roaming)
Huy Do12cb2652022-07-22 02:19:50 +0000230 elif system == "darwin":
231 path = os.path.expanduser("~/Library/Preferences/")
SsnL13013842020-04-30 11:27:13 -0700232 if appname:
233 path = os.path.join(path, appname)
234 else:
Huy Do12cb2652022-07-22 02:19:50 +0000235 path = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
SsnL13013842020-04-30 11:27:13 -0700236 if appname:
237 path = os.path.join(path, appname)
238 if appname and version:
239 path = os.path.join(path, version)
240 return path
241
242
243def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
244 r"""Return full path to the user-shared data dir for this application.
245
246 "appname" is the name of application.
247 If None, just the system directory is returned.
248 "appauthor" (only used on Windows) is the name of the
249 appauthor or distributing body for this application. Typically
250 it is the owning company name. This falls back to appname. You may
251 pass False to disable it.
252 "version" is an optional version path element to append to the
253 path. You might want to use this if you want multiple versions
254 of your app to be able to run independently. If used, this
255 would typically be "<major>.<minor>".
256 Only applied when appname is present.
257 "multipath" is an optional parameter only applicable to *nix
258 which indicates that the entire list of config dirs should be
259 returned. By default, the first item from XDG_CONFIG_DIRS is
260 returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
261
262 Typical site config directories are:
263 Mac OS X: same as site_data_dir
264 Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
265 $XDG_CONFIG_DIRS
266 Win *: same as site_data_dir
267 Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
268
269 For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
270
271 WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
272 """
Huy Do12cb2652022-07-22 02:19:50 +0000273 if system == "win32":
SsnL13013842020-04-30 11:27:13 -0700274 path = site_data_dir(appname, appauthor)
275 if appname and version:
276 path = os.path.join(path, version)
Huy Do12cb2652022-07-22 02:19:50 +0000277 elif system == "darwin":
278 path = os.path.expanduser("/Library/Preferences")
SsnL13013842020-04-30 11:27:13 -0700279 if appname:
280 path = os.path.join(path, appname)
281 else:
282 # XDG default for $XDG_CONFIG_DIRS
283 # only first, if multipath is False
Huy Do12cb2652022-07-22 02:19:50 +0000284 path = os.getenv("XDG_CONFIG_DIRS", "/etc/xdg")
285 pathlist = [
286 os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)
287 ]
SsnL13013842020-04-30 11:27:13 -0700288 if appname:
289 if version:
290 appname = os.path.join(appname, version)
291 pathlist = [os.sep.join([x, appname]) for x in pathlist]
292
293 if multipath:
294 path = os.pathsep.join(pathlist)
295 else:
296 path = pathlist[0]
297 return path
298
299
300def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
301 r"""Return full path to the user-specific cache dir for this application.
302
303 "appname" is the name of application.
304 If None, just the system directory is returned.
305 "appauthor" (only used on Windows) is the name of the
306 appauthor or distributing body for this application. Typically
307 it is the owning company name. This falls back to appname. You may
308 pass False to disable it.
309 "version" is an optional version path element to append to the
310 path. You might want to use this if you want multiple versions
311 of your app to be able to run independently. If used, this
312 would typically be "<major>.<minor>".
313 Only applied when appname is present.
314 "opinion" (boolean) can be False to disable the appending of
315 "Cache" to the base app data dir for Windows. See
316 discussion below.
317
318 Typical user cache directories are:
319 Mac OS X: ~/Library/Caches/<AppName>
320 Unix: ~/.cache/<AppName> (XDG default)
321 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
322 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
323
324 On Windows the only suggestion in the MSDN docs is that local settings go in
325 the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
326 app data dir (the default returned by `user_data_dir` above). Apps typically
327 put cache data somewhere *under* the given dir here. Some examples:
328 ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
329 ...\Acme\SuperApp\Cache\1.0
330 OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
331 This can be disabled with the `opinion=False` option.
332 """
333 if system == "win32":
334 if appauthor is None:
335 appauthor = appname
336 path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
337 if appname:
338 if appauthor is not False:
339 path = os.path.join(path, appauthor, appname)
340 else:
341 path = os.path.join(path, appname)
342 if opinion:
343 path = os.path.join(path, "Cache")
Huy Do12cb2652022-07-22 02:19:50 +0000344 elif system == "darwin":
345 path = os.path.expanduser("~/Library/Caches")
SsnL13013842020-04-30 11:27:13 -0700346 if appname:
347 path = os.path.join(path, appname)
348 else:
Huy Do12cb2652022-07-22 02:19:50 +0000349 path = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
SsnL13013842020-04-30 11:27:13 -0700350 if appname:
351 path = os.path.join(path, appname)
352 if appname and version:
353 path = os.path.join(path, version)
354 return path
355
356
357def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
358 r"""Return full path to the user-specific state dir for this application.
359
360 "appname" is the name of application.
361 If None, just the system directory is returned.
362 "appauthor" (only used on Windows) is the name of the
363 appauthor or distributing body for this application. Typically
364 it is the owning company name. This falls back to appname. You may
365 pass False to disable it.
366 "version" is an optional version path element to append to the
367 path. You might want to use this if you want multiple versions
368 of your app to be able to run independently. If used, this
369 would typically be "<major>.<minor>".
370 Only applied when appname is present.
371 "roaming" (boolean, default False) can be set True to use the Windows
372 roaming appdata directory. That means that for users on a Windows
373 network setup for roaming profiles, this user data will be
374 sync'd on login. See
375 <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
376 for a discussion of issues.
377
378 Typical user state directories are:
379 Mac OS X: same as user_data_dir
380 Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined
381 Win *: same as user_data_dir
382
383 For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state>
384 to extend the XDG spec and support $XDG_STATE_HOME.
385
386 That means, by default "~/.local/state/<AppName>".
387 """
388 if system in ["win32", "darwin"]:
389 path = user_data_dir(appname, appauthor, None, roaming)
390 else:
Huy Do12cb2652022-07-22 02:19:50 +0000391 path = os.getenv("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
SsnL13013842020-04-30 11:27:13 -0700392 if appname:
393 path = os.path.join(path, appname)
394 if appname and version:
395 path = os.path.join(path, version)
396 return path
397
398
399def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
400 r"""Return full path to the user-specific log dir for this application.
401
402 "appname" is the name of application.
403 If None, just the system directory is returned.
404 "appauthor" (only used on Windows) is the name of the
405 appauthor or distributing body for this application. Typically
406 it is the owning company name. This falls back to appname. You may
407 pass False to disable it.
408 "version" is an optional version path element to append to the
409 path. You might want to use this if you want multiple versions
410 of your app to be able to run independently. If used, this
411 would typically be "<major>.<minor>".
412 Only applied when appname is present.
413 "opinion" (boolean) can be False to disable the appending of
414 "Logs" to the base app data dir for Windows, and "log" to the
415 base cache dir for Unix. See discussion below.
416
417 Typical user log directories are:
418 Mac OS X: ~/Library/Logs/<AppName>
419 Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
420 Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
421 Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
422
423 On Windows the only suggestion in the MSDN docs is that local settings
424 go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
425 examples of what some windows apps use for a logs dir.)
426
427 OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
428 value for Windows and appends "log" to the user cache dir for Unix.
429 This can be disabled with the `opinion=False` option.
430 """
431 if system == "darwin":
Huy Do12cb2652022-07-22 02:19:50 +0000432 path = os.path.join(os.path.expanduser("~/Library/Logs"), appname)
SsnL13013842020-04-30 11:27:13 -0700433 elif system == "win32":
434 path = user_data_dir(appname, appauthor, version)
435 version = False
436 if opinion:
437 path = os.path.join(path, "Logs")
438 else:
439 path = user_cache_dir(appname, appauthor, version)
440 version = False
441 if opinion:
442 path = os.path.join(path, "log")
443 if appname and version:
444 path = os.path.join(path, version)
445 return path
446
447
448class AppDirs(object):
449 """Convenience wrapper for getting application dirs."""
Huy Do12cb2652022-07-22 02:19:50 +0000450
451 def __init__(
452 self, appname=None, appauthor=None, version=None, roaming=False, multipath=False
453 ):
SsnL13013842020-04-30 11:27:13 -0700454 self.appname = appname
455 self.appauthor = appauthor
456 self.version = version
457 self.roaming = roaming
458 self.multipath = multipath
459
460 @property
461 def user_data_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000462 return user_data_dir(
463 self.appname, self.appauthor, version=self.version, roaming=self.roaming
464 )
SsnL13013842020-04-30 11:27:13 -0700465
466 @property
467 def site_data_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000468 return site_data_dir(
469 self.appname, self.appauthor, version=self.version, multipath=self.multipath
470 )
SsnL13013842020-04-30 11:27:13 -0700471
472 @property
473 def user_config_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000474 return user_config_dir(
475 self.appname, self.appauthor, version=self.version, roaming=self.roaming
476 )
SsnL13013842020-04-30 11:27:13 -0700477
478 @property
479 def site_config_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000480 return site_config_dir(
481 self.appname, self.appauthor, version=self.version, multipath=self.multipath
482 )
SsnL13013842020-04-30 11:27:13 -0700483
484 @property
485 def user_cache_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000486 return user_cache_dir(self.appname, self.appauthor, version=self.version)
SsnL13013842020-04-30 11:27:13 -0700487
488 @property
489 def user_state_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000490 return user_state_dir(self.appname, self.appauthor, version=self.version)
SsnL13013842020-04-30 11:27:13 -0700491
492 @property
493 def user_log_dir(self):
Huy Do12cb2652022-07-22 02:19:50 +0000494 return user_log_dir(self.appname, self.appauthor, version=self.version)
SsnL13013842020-04-30 11:27:13 -0700495
496
Huy Do12cb2652022-07-22 02:19:50 +0000497# ---- internal support stuff
498
SsnL13013842020-04-30 11:27:13 -0700499
500def _get_win_folder_from_registry(csidl_name):
501 """This is a fallback technique at best. I'm not sure if using the
502 registry for this guarantees us the correct answer for all CSIDL_*
503 names.
504 """
Nikita Shulgac6b69a42020-06-15 08:14:54 -0700505 import winreg as _winreg
SsnL13013842020-04-30 11:27:13 -0700506
507 shell_folder_name = {
508 "CSIDL_APPDATA": "AppData",
509 "CSIDL_COMMON_APPDATA": "Common AppData",
510 "CSIDL_LOCAL_APPDATA": "Local AppData",
511 }[csidl_name]
512
513 key = _winreg.OpenKey(
514 _winreg.HKEY_CURRENT_USER,
Huy Do12cb2652022-07-22 02:19:50 +0000515 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
SsnL13013842020-04-30 11:27:13 -0700516 )
517 dir, type = _winreg.QueryValueEx(key, shell_folder_name)
518 return dir
519
520
521def _get_win_folder_with_pywin32(csidl_name):
Huy Do12cb2652022-07-22 02:19:50 +0000522 from win32com.shell import shell, shellcon
523
SsnL13013842020-04-30 11:27:13 -0700524 dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
525 # Try to make this a unicode path because SHGetFolderPath does
526 # not return unicode strings when there is unicode data in the
527 # path.
528 try:
529 dir = unicode(dir)
530
531 # Downgrade to short path name if have highbit chars. See
532 # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
533 has_high_char = False
534 for c in dir:
535 if ord(c) > 255:
536 has_high_char = True
537 break
538 if has_high_char:
539 try:
540 import win32api
Huy Do12cb2652022-07-22 02:19:50 +0000541
SsnL13013842020-04-30 11:27:13 -0700542 dir = win32api.GetShortPathName(dir)
543 except ImportError:
544 pass
545 except UnicodeError:
546 pass
547 return dir
548
549
550def _get_win_folder_with_ctypes(csidl_name):
551 import ctypes
552
553 csidl_const = {
554 "CSIDL_APPDATA": 26,
555 "CSIDL_COMMON_APPDATA": 35,
556 "CSIDL_LOCAL_APPDATA": 28,
557 }[csidl_name]
558
559 buf = ctypes.create_unicode_buffer(1024)
560 ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
561
562 # Downgrade to short path name if have highbit chars. See
563 # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
564 has_high_char = False
565 for c in buf:
566 if ord(c) > 255:
567 has_high_char = True
568 break
569 if has_high_char:
570 buf2 = ctypes.create_unicode_buffer(1024)
571 if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
572 buf = buf2
573
574 return buf.value
575
Huy Do12cb2652022-07-22 02:19:50 +0000576
SsnL13013842020-04-30 11:27:13 -0700577def _get_win_folder_with_jna(csidl_name):
578 import array
Huy Do12cb2652022-07-22 02:19:50 +0000579
SsnL13013842020-04-30 11:27:13 -0700580 from com.sun import jna
581 from com.sun.jna.platform import win32
582
583 buf_size = win32.WinDef.MAX_PATH * 2
Huy Do12cb2652022-07-22 02:19:50 +0000584 buf = array.zeros("c", buf_size)
SsnL13013842020-04-30 11:27:13 -0700585 shell = win32.Shell32.INSTANCE
Huy Do12cb2652022-07-22 02:19:50 +0000586 shell.SHGetFolderPath(
587 None,
588 getattr(win32.ShlObj, csidl_name),
589 None,
590 win32.ShlObj.SHGFP_TYPE_CURRENT,
591 buf,
592 )
SsnL13013842020-04-30 11:27:13 -0700593 dir = jna.Native.toString(buf.tostring()).rstrip("\0")
594
595 # Downgrade to short path name if have highbit chars. See
596 # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
597 has_high_char = False
598 for c in dir:
599 if ord(c) > 255:
600 has_high_char = True
601 break
602 if has_high_char:
Huy Do12cb2652022-07-22 02:19:50 +0000603 buf = array.zeros("c", buf_size)
SsnL13013842020-04-30 11:27:13 -0700604 kernel = win32.Kernel32.INSTANCE
605 if kernel.GetShortPathName(dir, buf, buf_size):
606 dir = jna.Native.toString(buf.tostring()).rstrip("\0")
607
608 return dir
609
Huy Do12cb2652022-07-22 02:19:50 +0000610
SsnL13013842020-04-30 11:27:13 -0700611if system == "win32":
612 try:
613 import win32com.shell
Huy Do12cb2652022-07-22 02:19:50 +0000614
SsnL13013842020-04-30 11:27:13 -0700615 _get_win_folder = _get_win_folder_with_pywin32
616 except ImportError:
617 try:
618 from ctypes import windll
Huy Do12cb2652022-07-22 02:19:50 +0000619
SsnL13013842020-04-30 11:27:13 -0700620 _get_win_folder = _get_win_folder_with_ctypes
621 except ImportError:
622 try:
623 import com.sun.jna
Huy Do12cb2652022-07-22 02:19:50 +0000624
SsnL13013842020-04-30 11:27:13 -0700625 _get_win_folder = _get_win_folder_with_jna
626 except ImportError:
627 _get_win_folder = _get_win_folder_from_registry
628
629
Huy Do12cb2652022-07-22 02:19:50 +0000630# ---- self test code
SsnL13013842020-04-30 11:27:13 -0700631
632if __name__ == "__main__":
633 appname = "MyApp"
634 appauthor = "MyCompany"
635
Huy Do12cb2652022-07-22 02:19:50 +0000636 props = (
637 "user_data_dir",
638 "user_config_dir",
639 "user_cache_dir",
640 "user_state_dir",
641 "user_log_dir",
642 "site_data_dir",
643 "site_config_dir",
644 )
SsnL13013842020-04-30 11:27:13 -0700645
Justin Chu4cc17452023-07-21 08:23:48 -0700646 print(f"-- app dirs {__version__} --")
SsnL13013842020-04-30 11:27:13 -0700647
648 print("-- app dirs (with optional 'version')")
649 dirs = AppDirs(appname, appauthor, version="1.0")
650 for prop in props:
Justin Chu4cc17452023-07-21 08:23:48 -0700651 print(f"{prop}: {getattr(dirs, prop)}")
SsnL13013842020-04-30 11:27:13 -0700652
653 print("\n-- app dirs (without optional 'version')")
654 dirs = AppDirs(appname, appauthor)
655 for prop in props:
Justin Chu4cc17452023-07-21 08:23:48 -0700656 print(f"{prop}: {getattr(dirs, prop)}")
SsnL13013842020-04-30 11:27:13 -0700657
658 print("\n-- app dirs (without optional 'appauthor')")
659 dirs = AppDirs(appname)
660 for prop in props:
Justin Chu4cc17452023-07-21 08:23:48 -0700661 print(f"{prop}: {getattr(dirs, prop)}")
SsnL13013842020-04-30 11:27:13 -0700662
663 print("\n-- app dirs (with disabled 'appauthor')")
664 dirs = AppDirs(appname, appauthor=False)
665 for prop in props:
Justin Chu4cc17452023-07-21 08:23:48 -0700666 print(f"{prop}: {getattr(dirs, prop)}")