126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
import inspect
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
from enum import IntFlag, Enum, unique
|
|
|
|
from helpers.ConfigHelper import is_windows
|
|
|
|
|
|
error_message = "'%s' function is only supported in Windows OS."
|
|
|
|
# ==========================
|
|
# Structures
|
|
# ==========================
|
|
class FILETIME(ctypes.Structure):
|
|
_fields_ = [
|
|
("dwLowDateTime", wintypes.DWORD),
|
|
("dwHighDateTime", wintypes.DWORD),
|
|
]
|
|
|
|
class WIN32_FILE_ATTRIBUTE_DATA(ctypes.Structure):
|
|
_fields_ = [
|
|
("dwFileAttributes", wintypes.DWORD),
|
|
("ftCreationTime", FILETIME),
|
|
("ftLastAccessTime", FILETIME),
|
|
("ftLastWriteTime", FILETIME),
|
|
("nFileSizeHigh", wintypes.DWORD),
|
|
("nFileSizeLow", wintypes.DWORD),
|
|
]
|
|
|
|
# Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
|
@unique
|
|
class FileAttributeConstants(IntFlag):
|
|
__str__ = Enum.__str__
|
|
FILE_ATTRIBUTE_PINNED = 0x00080000
|
|
FILE_ATTRIBUTE_UNPINNED = 0x00100000
|
|
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
|
|
|
|
GetFileAttributesExW = None
|
|
GetCompressedFileSizeW = None
|
|
|
|
if is_windows():
|
|
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
|
|
GetFileAttributesExW = kernel32.GetFileAttributesExW
|
|
GetFileAttributesExW.argtypes = [
|
|
wintypes.LPCWSTR,
|
|
ctypes.c_int,
|
|
ctypes.POINTER(WIN32_FILE_ATTRIBUTE_DATA),
|
|
]
|
|
GetFileAttributesExW.restype = wintypes.BOOL
|
|
|
|
GetCompressedFileSizeW = kernel32.GetCompressedFileSizeW
|
|
GetCompressedFileSizeW.argtypes = [
|
|
wintypes.LPCWSTR,
|
|
ctypes.POINTER(wintypes.DWORD),
|
|
]
|
|
GetCompressedFileSizeW.restype = wintypes.DWORD
|
|
|
|
|
|
def get_file_attributes(path):
|
|
if is_windows():
|
|
data = WIN32_FILE_ATTRIBUTE_DATA()
|
|
success = GetFileAttributesExW(path, 0, ctypes.byref(data))
|
|
if not success:
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
attributes = FileAttributeConstants(data.dwFileAttributes)
|
|
mask = (
|
|
FileAttributeConstants.FILE_ATTRIBUTE_PINNED |
|
|
FileAttributeConstants.FILE_ATTRIBUTE_UNPINNED |
|
|
FileAttributeConstants.FILE_ATTRIBUTE_ARCHIVE
|
|
)
|
|
return attributes & mask
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def get_compressed_file_size(path):
|
|
if is_windows():
|
|
high = wintypes.DWORD(0)
|
|
low = GetCompressedFileSizeW(path, ctypes.byref(high))
|
|
|
|
if low == 0xFFFFFFFF:
|
|
err = ctypes.get_last_error()
|
|
if err != 0:
|
|
raise ctypes.WinError(err)
|
|
|
|
return (high.value << 32) | low
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def resource_archived(resource_path):
|
|
if is_windows():
|
|
return bool(get_file_attributes(resource_path) & FileAttributeConstants.FILE_ATTRIBUTE_ARCHIVE)
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def resource_pinned(resource_path):
|
|
if is_windows():
|
|
return bool(get_file_attributes(resource_path) & FileAttributeConstants.FILE_ATTRIBUTE_PINNED)
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def resource_unpinned(resource_path):
|
|
if is_windows():
|
|
return bool(get_file_attributes(resource_path) & FileAttributeConstants.FILE_ATTRIBUTE_UNPINNED)
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def is_placeholder_resource(resource_path):
|
|
if is_windows():
|
|
size_on_disk = get_compressed_file_size(resource_path)
|
|
unpinned = resource_unpinned(resource_path)
|
|
pinned = resource_pinned(resource_path)
|
|
archived = resource_archived(resource_path)
|
|
return (not size_on_disk or unpinned) and not (pinned and archived)
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|
|
|
|
|
|
def is_file_downloaded(resource_path):
|
|
if is_windows():
|
|
size_on_disk = get_compressed_file_size(resource_path)
|
|
pinned = resource_pinned(resource_path)
|
|
unpinned = resource_unpinned(resource_path)
|
|
archived = resource_archived(resource_path)
|
|
return size_on_disk and (pinned or archived) and not unpinned
|
|
raise OSError(error_message % inspect.currentframe().f_back.f_code.co_name)
|