mirror of
https://github.com/arsenetar/send2trash.git
synced 2025-05-08 01:39:51 +00:00
Split plat_win into legacy and modern
This commit is contained in:
parent
6a1b47bc5e
commit
1c5fea461f
@ -5,171 +5,16 @@
|
|||||||
# http://www.hardcoded.net/licenses/bsd_license
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import os.path as op
|
|
||||||
from .compat import text_type
|
|
||||||
from platform import version
|
from platform import version
|
||||||
|
|
||||||
legacy = False
|
|
||||||
# if windows is vista or newer and pywin32 is available use IFileOperation
|
# if windows is vista or newer and pywin32 is available use IFileOperation
|
||||||
if int(version().split(".", 1)[0]) >= 6:
|
if int(version().split(".", 1)[0]) >= 6:
|
||||||
try:
|
try:
|
||||||
# Attempt to use pywin32 to use IFileOperation
|
# Attempt to use pywin32 to use IFileOperation
|
||||||
import pythoncom
|
from .plat_win_modern import send2trash
|
||||||
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)
|
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
legacy = True
|
# use SHFileOperation as fallback
|
||||||
|
from .plat_win_legacy import send2trash
|
||||||
else:
|
else:
|
||||||
legacy = True
|
# use SHFileOperation as fallback
|
||||||
|
from .plat_win_legacy import send2trash
|
||||||
# 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)
|
|
||||||
|
105
send2trash/plat_win_legacy.py
Normal file
105
send2trash/plat_win_legacy.py
Normal 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)
|
65
send2trash/plat_win_modern.py
Normal file
65
send2trash/plat_win_modern.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user