mirror of
https://github.com/arsenetar/send2trash.git
synced 2025-05-08 09:49:52 +00:00
Initial IFileOperation for Windows
- Try using IFileOperation instead of SHFileOperation - Use pywin32 to accomplish this - Implement fallback when pywin32 not available - Handles paths like `C:\` just fine bu the `\\?\` paths in the test cause issue - Add batching for IFileOperation version (performance) - Minor formatting applied by editor
This commit is contained in:
parent
66afce7252
commit
7abc048836
@ -5,23 +5,91 @@
|
||||
# http://www.hardcoded.net/licenses/bsd_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ctypes import (windll, Structure, byref, c_uint,
|
||||
create_unicode_buffer, addressof,
|
||||
GetLastError, FormatError)
|
||||
from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
|
||||
import os.path as op
|
||||
|
||||
from .compat import text_type
|
||||
|
||||
kernel32 = windll.kernel32
|
||||
GetShortPathNameW = kernel32.GetShortPathNameW
|
||||
try:
|
||||
# Attempt to use pywin32 to use IFileOperation
|
||||
import pythoncom
|
||||
import pywintypes
|
||||
from win32com.shell import shell, shellcon
|
||||
from platform import version
|
||||
|
||||
shell32 = windll.shell32
|
||||
SHFileOperationW = shell32.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]
|
||||
# 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 and False:
|
||||
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)
|
||||
|
||||
|
||||
class SHFILEOPSTRUCTW(Structure):
|
||||
except ImportError:
|
||||
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),
|
||||
@ -33,22 +101,20 @@ class SHFILEOPSTRUCTW(Structure):
|
||||
("lpszProgressTitle", LPCWSTR),
|
||||
]
|
||||
|
||||
FO_MOVE = 1
|
||||
FO_COPY = 2
|
||||
FO_DELETE = 3
|
||||
FO_RENAME = 4
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
@ -59,10 +125,9 @@ def get_short_path_name(long_name):
|
||||
GetShortPathNameW(long_name, output, buf_size)
|
||||
return output.value[4:] # Remove '\\?\' for SHFileOperationW
|
||||
|
||||
|
||||
def send2trash(path):
|
||||
def send2trash(path):
|
||||
if not isinstance(path, text_type):
|
||||
path = text_type(path, 'mbcs')
|
||||
path = text_type(path, "mbcs")
|
||||
if not op.isabs(path):
|
||||
path = op.abspath(path)
|
||||
path = get_short_path_name(path)
|
||||
@ -80,7 +145,7 @@ def send2trash(path):
|
||||
# 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)
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user