Split plat_win into legacy and modern

This commit is contained in:
Andrew Senetar 2020-05-22 12:22:32 -05:00
parent 6a1b47bc5e
commit 1c5fea461f
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
3 changed files with 175 additions and 160 deletions

View File

@ -5,171 +5,16 @@
# http://www.hardcoded.net/licenses/bsd_license
from __future__ import unicode_literals
import os.path as op
from .compat import text_type
from platform import version
legacy = False
# if windows is vista or newer and pywin32 is available use IFileOperation
if int(version().split(".", 1)[0]) >= 6:
try:
# Attempt to use pywin32 to use IFileOperation
import pythoncom
import pywintypes
from win32com.shell import shell, shellcon
def send2trash(path):
if not isinstance(path, list):
path = [path]
# convert data type
path = [
text_type(item, "mbcs") if not isinstance(item, text_type) else item
for item in path
]
# convert to full paths
path = [op.abspath(item) if not op.isabs(item) else item for item in path]
# remove the leading \\?\ if present
path = [item[4:] if item.startswith("\\\\?\\") else item for item in path]
# create instance of file operation object
fileop = pythoncom.CoCreateInstance(
shell.CLSID_FileOperation,
None,
pythoncom.CLSCTX_ALL,
shell.IID_IFileOperation,
)
# default flags to use
flags = (
shellcon.FOF_NOCONFIRMATION
| shellcon.FOF_NOERRORUI
| shellcon.FOF_SILENT
| shellcon.FOFX_EARLYFAILURE
)
# determine rest of the flags based on OS version
# use newer recommended flags if available
if int(version().split(".", 1)[0]) >= 8:
flags |= (
0x20000000 # FOFX_ADDUNDORECORD win 8+
| 0x00080000 # FOFX_RECYCLEONDELETE win 8+
)
else:
flags |= shellcon.FOF_ALLOWUNDO
# set the flags
fileop.SetOperationFlags(flags)
# actually try to perform the operation, this section may throw a
# pywintypes.com_error which does not seem to create as nice of an
# error as OSError so wrapping with try to convert
try:
for itemPath in path:
item = shell.SHCreateItemFromParsingName(
itemPath, None, shell.IID_IShellItem
)
fileop.DeleteItem(item)
result = fileop.PerformOperations()
aborted = fileop.GetAnyOperationsAborted()
# if non-zero result or aborted throw an exception
if result or aborted:
raise OSError(None, None, path, result)
except pywintypes.com_error as error:
# convert to standard OS error, allows other code to get a
# normal errno
raise OSError(None, error.strerror, path, error.hresult)
from .plat_win_modern import send2trash
except ImportError:
legacy = True
# use SHFileOperation as fallback
from .plat_win_legacy import send2trash
else:
legacy = True
# use SHFileOperation as fallback
if legacy:
from ctypes import (
windll,
Structure,
byref,
c_uint,
create_unicode_buffer,
addressof,
GetLastError,
FormatError,
)
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
kernel32 = windll.kernel32
GetShortPathNameW = kernel32.GetShortPathNameW
shell32 = windll.shell32
SHFileOperationW = shell32.SHFileOperationW
class SHFILEOPSTRUCTW(Structure):
_fields_ = [
("hwnd", HWND),
("wFunc", UINT),
("pFrom", LPCWSTR),
("pTo", LPCWSTR),
("fFlags", c_uint),
("fAnyOperationsAborted", BOOL),
("hNameMappings", c_uint),
("lpszProgressTitle", LPCWSTR),
]
FO_MOVE = 1
FO_COPY = 2
FO_DELETE = 3
FO_RENAME = 4
FOF_MULTIDESTFILES = 1
FOF_SILENT = 4
FOF_NOCONFIRMATION = 16
FOF_ALLOWUNDO = 64
FOF_NOERRORUI = 1024
def get_short_path_name(long_name):
if not long_name.startswith("\\\\?\\"):
long_name = "\\\\?\\" + long_name
buf_size = GetShortPathNameW(long_name, None, 0)
# FIX: https://github.com/hsoft/send2trash/issues/31
# If buffer size is zero, an error has occurred.
if not buf_size:
err_no = GetLastError()
raise WindowsError(err_no, FormatError(err_no), long_name[4:])
output = create_unicode_buffer(buf_size)
GetShortPathNameW(long_name, output, buf_size)
return output.value[4:] # Remove '\\?\' for SHFileOperationW
def send2trash(path):
if not isinstance(path, list):
path = [path]
# convert data type
path = [
text_type(item, "mbcs") if not isinstance(item, text_type) else item
for item in path
]
# convert to full paths
path = [op.abspath(item) if not op.isabs(item) else item for item in path]
# get short path to handle path length issues
path = [get_short_path_name(item) for item in path]
# convert to a single string of null terminated paths
path = "\0".join(path)
fileop = SHFILEOPSTRUCTW()
fileop.hwnd = 0
fileop.wFunc = FO_DELETE
# FIX: https://github.com/hsoft/send2trash/issues/17
# Starting in python 3.6.3 it is no longer possible to use:
# LPCWSTR(path + '\0') directly as embedded null characters are no longer
# allowed in strings
# Workaround
# - create buffer of c_wchar[] (LPCWSTR is based on this type)
# - buffer is two c_wchar characters longer (double null terminator)
# - cast the address of the buffer to a LPCWSTR
# NOTE: based on how python allocates memory for these types they should
# always be zero, if this is ever not true we can go back to explicitly
# setting the last two characters to null using buffer[index] = '\0'.
buffer = create_unicode_buffer(path, len(path) + 2)
fileop.pFrom = LPCWSTR(addressof(buffer))
fileop.pTo = None
fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT
fileop.fAnyOperationsAborted = 0
fileop.hNameMappings = 0
fileop.lpszProgressTitle = None
result = SHFileOperationW(byref(fileop))
if result:
raise WindowsError(result, FormatError(result), path)
# use SHFileOperation as fallback
from .plat_win_legacy import send2trash

View File

@ -0,0 +1,105 @@
# Copyright 2017 Virgil Dupras
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from __future__ import unicode_literals
import os.path as op
from .compat import text_type
from ctypes import (
windll,
Structure,
byref,
c_uint,
create_unicode_buffer,
addressof,
GetLastError,
FormatError,
)
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
kernel32 = windll.kernel32
GetShortPathNameW = kernel32.GetShortPathNameW
shell32 = windll.shell32
SHFileOperationW = shell32.SHFileOperationW
class SHFILEOPSTRUCTW(Structure):
_fields_ = [
("hwnd", HWND),
("wFunc", UINT),
("pFrom", LPCWSTR),
("pTo", LPCWSTR),
("fFlags", c_uint),
("fAnyOperationsAborted", BOOL),
("hNameMappings", c_uint),
("lpszProgressTitle", LPCWSTR),
]
FO_MOVE = 1
FO_COPY = 2
FO_DELETE = 3
FO_RENAME = 4
FOF_MULTIDESTFILES = 1
FOF_SILENT = 4
FOF_NOCONFIRMATION = 16
FOF_ALLOWUNDO = 64
FOF_NOERRORUI = 1024
def get_short_path_name(long_name):
if not long_name.startswith("\\\\?\\"):
long_name = "\\\\?\\" + long_name
buf_size = GetShortPathNameW(long_name, None, 0)
# FIX: https://github.com/hsoft/send2trash/issues/31
# If buffer size is zero, an error has occurred.
if not buf_size:
err_no = GetLastError()
raise WindowsError(err_no, FormatError(err_no), long_name[4:])
output = create_unicode_buffer(buf_size)
GetShortPathNameW(long_name, output, buf_size)
return output.value[4:] # Remove '\\?\' for SHFileOperationW
def send2trash(paths):
if not isinstance(paths, list):
paths = [paths]
# convert data type
paths = [
text_type(path, "mbcs") if not isinstance(path, text_type) else path
for path in paths
]
# convert to full paths
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
# get short path to handle path length issues
paths = [get_short_path_name(path) for path in paths]
# convert to a single string of null terminated paths
paths = "\0".join(paths)
fileop = SHFILEOPSTRUCTW()
fileop.hwnd = 0
fileop.wFunc = FO_DELETE
# FIX: https://github.com/hsoft/send2trash/issues/17
# Starting in python 3.6.3 it is no longer possible to use:
# LPCWSTR(path + '\0') directly as embedded null characters are no longer
# allowed in strings
# Workaround
# - create buffer of c_wchar[] (LPCWSTR is based on this type)
# - buffer is two c_wchar characters longer (double null terminator)
# - cast the address of the buffer to a LPCWSTR
# NOTE: based on how python allocates memory for these types they should
# always be zero, if this is ever not true we can go back to explicitly
# setting the last two characters to null using buffer[index] = '\0'.
buffer = create_unicode_buffer(paths, len(paths) + 2)
fileop.pFrom = LPCWSTR(addressof(buffer))
fileop.pTo = None
fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT
fileop.fAnyOperationsAborted = 0
fileop.hNameMappings = 0
fileop.lpszProgressTitle = None
result = SHFileOperationW(byref(fileop))
if result:
raise WindowsError(result, FormatError(result), paths)

View File

@ -0,0 +1,65 @@
# Copyright 2017 Virgil Dupras
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
from __future__ import unicode_literals
import os.path as op
from .compat import text_type
from platform import version
import pythoncom
import pywintypes
from win32com.shell import shell, shellcon
def send2trash(paths):
if not isinstance(paths, list):
paths = [paths]
# convert data type
paths = [
text_type(path, "mbcs") if not isinstance(path, text_type) else path
for path in paths
]
# convert to full paths
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
# remove the leading \\?\ if present
paths = [path[4:] if path.startswith("\\\\?\\") else path for path in paths]
# create instance of file operation object
fileop = pythoncom.CoCreateInstance(
shell.CLSID_FileOperation, None, pythoncom.CLSCTX_ALL, shell.IID_IFileOperation,
)
# default flags to use
flags = (
shellcon.FOF_NOCONFIRMATION
| shellcon.FOF_NOERRORUI
| shellcon.FOF_SILENT
| shellcon.FOFX_EARLYFAILURE
)
# determine rest of the flags based on OS version
# use newer recommended flags if available
if int(version().split(".", 1)[0]) >= 8:
flags |= (
0x20000000 # FOFX_ADDUNDORECORD win 8+
| 0x00080000 # FOFX_RECYCLEONDELETE win 8+
)
else:
flags |= shellcon.FOF_ALLOWUNDO
# set the flags
fileop.SetOperationFlags(flags)
# actually try to perform the operation, this section may throw a
# pywintypes.com_error which does not seem to create as nice of an
# error as OSError so wrapping with try to convert
try:
for path in paths:
item = shell.SHCreateItemFromParsingName(path, None, shell.IID_IShellItem)
fileop.DeleteItem(item)
result = fileop.PerformOperations()
aborted = fileop.GetAnyOperationsAborted()
# if non-zero result or aborted throw an exception
if result or aborted:
raise OSError(None, None, paths, result)
except pywintypes.com_error as error:
# convert to standard OS error, allows other code to get a
# normal errno
raise OSError(None, error.strerror, path, error.hresult)