Add windows version check, legacy list support

- Add check for windows version for IFileOperation
- Add list support to legacy version
- Remove some debugging code
- Fix bug in path converson

Not sure if there is a better way to layout this file
This commit is contained in:
Andrew Senetar 2020-05-22 00:10:13 -05:00
parent 629c2403e9
commit 6a1b47bc5e
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
1 changed files with 81 additions and 65 deletions

View File

@ -7,72 +7,80 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os.path as op import os.path as op
from .compat import text_type from .compat import text_type
from platform import version
try: legacy = False
# Attempt to use pywin32 to use IFileOperation # if windows is vista or newer and pywin32 is available use IFileOperation
import pythoncom if int(version().split(".", 1)[0]) >= 6:
import pywintypes try:
from win32com.shell import shell, shellcon # Attempt to use pywin32 to use IFileOperation
from platform import version import pythoncom
import pywintypes
from win32com.shell import shell, shellcon
def send2trash(path): def send2trash(path):
if not isinstance(path, list): if not isinstance(path, list):
path = [path] path = [path]
# convert data type # convert data type
path = [ path = [
text_type(item, "mbcs") if not isinstance(item, text_type) else item text_type(item, "mbcs") if not isinstance(item, text_type) else item
for item in path for item in path
] ]
# convert to full paths # convert to full paths
path = [op.abspath(item) if not op.isabs(item) else item for item in path] path = [op.abspath(item) if not op.isabs(item) else item for item in path]
# remove the leading \\?\ if present # remove the leading \\?\ if present
path = [item[4:] for item in path if item.startswith("\\\\?\\")] path = [item[4:] if item.startswith("\\\\?\\") else item for item in path]
# create instance of file operation object # create instance of file operation object
fileop = pythoncom.CoCreateInstance( fileop = pythoncom.CoCreateInstance(
shell.CLSID_FileOperation, shell.CLSID_FileOperation,
None, None,
pythoncom.CLSCTX_ALL, pythoncom.CLSCTX_ALL,
shell.IID_IFileOperation, 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 and False:
flags |= (
0x20000000 # FOFX_ADDUNDORECORD win 8+
| 0x00080000 # FOFX_RECYCLEONDELETE win 8+
) )
else: # default flags to use
flags |= shellcon.FOF_ALLOWUNDO flags = (
# set the flags shellcon.FOF_NOCONFIRMATION
fileop.SetOperationFlags(flags) | shellcon.FOF_NOERRORUI
# actually try to perform the operation, this section may throw a | shellcon.FOF_SILENT
# pywintypes.com_error which does not seem to create as nice of an | shellcon.FOFX_EARLYFAILURE
# error as OSError so wrapping with try to convert )
try: # determine rest of the flags based on OS version
for itemPath in path: # use newer recommended flags if available
item = shell.SHCreateItemFromParsingName( if int(version().split(".", 1)[0]) >= 8:
itemPath, None, shell.IID_IShellItem flags |= (
0x20000000 # FOFX_ADDUNDORECORD win 8+
| 0x00080000 # FOFX_RECYCLEONDELETE win 8+
) )
fileop.DeleteItem(item) else:
result = fileop.PerformOperations() flags |= shellcon.FOF_ALLOWUNDO
aborted = fileop.GetAnyOperationsAborted() # set the flags
# if non-zero result or aborted throw an exception fileop.SetOperationFlags(flags)
if result or aborted: # actually try to perform the operation, this section may throw a
raise OSError(None, None, path, result) # pywintypes.com_error which does not seem to create as nice of an
except pywintypes.com_error as error: # error as OSError so wrapping with try to convert
# convert to standard OS error, allows other code to get a try:
# normal errno for itemPath in path:
raise OSError(None, error.strerror, path, error.hresult) 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)
except ImportError:
legacy = True
else:
legacy = True
except ImportError: # use SHFileOperation as fallback
if legacy:
from ctypes import ( from ctypes import (
windll, windll,
Structure, Structure,
@ -128,11 +136,19 @@ except ImportError:
return output.value[4:] # Remove '\\?\' for SHFileOperationW return output.value[4:] # Remove '\\?\' for SHFileOperationW
def send2trash(path): def send2trash(path):
if not isinstance(path, text_type): if not isinstance(path, list):
path = text_type(path, "mbcs") path = [path]
if not op.isabs(path): # convert data type
path = op.abspath(path) path = [
path = get_short_path_name(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 = SHFILEOPSTRUCTW()
fileop.hwnd = 0 fileop.hwnd = 0
fileop.wFunc = FO_DELETE fileop.wFunc = FO_DELETE