Skip to content

Instantly share code, notes, and snippets.

@JamesTheAwesomeDude
Last active March 11, 2024 19:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesTheAwesomeDude/54dbbf6293281d7f5c7965474b0995f4 to your computer and use it in GitHub Desktop.
Save JamesTheAwesomeDude/54dbbf6293281d7f5c7965474b0995f4 to your computer and use it in GitHub Desktop.
Python get "user cache directory" on any OS
# Usage:
# import _cachedir
# CACHEDIR = _cachedir.cachedir('my_application@company.com')
# CACHEDIR = _cachedir.cachedir('my_packagename@pypi.org')
# NOTE:
# This does not create a secure enclave of any kind.
# Obviously, malicious applications running on the same
# user account could read or modify the cache directory.
import os
from pathlib import Path
import platform
import re
import sys
import uuid
# https://hg.mozilla.org/releases/mozilla-release/file/652f653a58f0acdc1413e45ab35eae68a95cd1af/toolkit/mozapps/extensions/internal/XPIInstall.jsm#l178
UNIQUE_REGEX = re.compile(r'^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$')
UMASK_PRIVDIR = 0o0700
__all__ = ['cachedir']
def cachedir(name):
if not UNIQUE_REGEX.match(name):
if '@' not in name and len(name) < 30:
raise ValueError(f'bad application ID. Did you mean {f"{name}@pypi.org"!r}?')
raise ValueError('bad application ID.')
app_dir = _base_usercachedir() / name
os.makedirs(app_dir, UMASK_PRIVDIR, exist_ok=True)
return app_dir
def _base_usercachedir():
system = platform.system()
if system == 'Windows':
# https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#FOLDERID_LOCALAPPDATA
return Path(_shell32_known_folder('F1B32785-6FBA-4FCF-9D55-7B8E7F157091'))
elif system == 'Darwin':
# FIXME https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/cachesdirectory
return Path('~/Library/Caches').expanduser()
else:
# https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
if not (system == 'Linux' or system.endswith('BSD') or system == 'AIX'):
warnings.warn(f'Unrecognized operating system, {system!r}')
if ['XDG_CACHE_HOME'] in os.environ:
return Path(os.environ['XDG_CACHE_HOME'])
else:
return Path('~/.cache').expanduser()
if platform.system() == 'Windows':
from contextlib import contextmanager
import ctypes
from ctypes import oledll
from ctypes import windll
import ctypes.wintypes
try:
wintypes_GUID = ctypes.wintypes.GUID
except AttributeError:
class wintypes_GUID(ctypes.Structure):
# https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
_fields_ = [
('Data1', ctypes.c_ulong),
('Data2', ctypes.c_ushort),
('Data3', ctypes.c_ushort),
('Data4', ctypes.c_ubyte * 8)
]
def __init__(self, guid=None):
super().__init__() # NOTE is this needed??
if guid is not None:
# https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring
if not isinstance(guid, uuid.UUID):
guid = uuid.UUID(guid)
clsid = f'{{{guid!s}}}'
errno = oledll.ole32.CLSIDFromString(clsid, ctypes.byref(self))
if errno != 0:
raise RuntimeError(f'CLSIDFromString returned error code {errno}')
def _shell32_known_folder(guid, /, *, _flags=0, _handle=None):
folder_id = wintypes_GUID(guid)
result_ptr = ctypes.c_wchar_p()
with _freeing(oledll.ole32.CoTaskMemFree, result_ptr):
errno = windll.shell32.SHGetKnownFolderPath(
ctypes.byref(folder_id),
_flags,
_handle,
ctypes.byref(result_ptr)
)
if errno == 0:
return result_ptr.value
else:
raise RuntimeError(f'SHGetKnownFolderPath returned error code {errno}')
@contextmanager
def _freeing(freefunc, obj):
try:
yield obj
finally:
freefunc(obj)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment