|
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import unicode_literals |
|
import os.path as op |
|
|
|
from send2trash.compat import text_type |
|
from send2trash.util import preprocess_paths |
|
|
|
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 convert_sh_file_opt_result(result): |
|
|
|
|
|
|
|
results = { |
|
0x71: 0x50, |
|
0x72: 0x57, |
|
0x73: 0x57, |
|
0x74: 0x57, |
|
0x75: 0x4C7, |
|
0x76: 0x57, |
|
0x78: 0x05, |
|
0x79: 0x6F, |
|
0x7A: 0x57, |
|
0x7C: 0xA1, |
|
0x7D: 0x57, |
|
0x7E: 0xB7, |
|
0x80: 0xB7, |
|
0x81: 0x6F, |
|
0x82: 0x13, |
|
0x83: 0x13, |
|
0x84: 0x6F9, |
|
0x85: 0xDF, |
|
0x86: 0x13, |
|
0x87: 0x13, |
|
0x88: 0x6F9, |
|
0xB7: 0x6F, |
|
0x402: 0xA1, |
|
0x10000: 0x1D, |
|
0x10074: 0x57, |
|
} |
|
|
|
return results.get(result, result) |
|
|
|
|
|
def prefix_and_path(path): |
|
r"""Guess the long-path prefix based on the kind of *path*. |
|
Local paths (C:\folder\file.ext) and UNC names (\\server\folder\file.ext) |
|
are handled. |
|
|
|
Return a tuple of the long-path prefix and the prefixed path. |
|
""" |
|
prefix, long_path = "\\\\?\\", path |
|
|
|
if not path.startswith(prefix): |
|
if path.startswith("\\\\"): |
|
|
|
prefix = "\\\\?\\UNC" |
|
long_path = prefix + path[1:] |
|
else: |
|
|
|
long_path = prefix + path |
|
elif path.startswith(prefix + "UNC\\"): |
|
|
|
prefix = "\\\\?\\UNC" |
|
|
|
return prefix, long_path |
|
|
|
|
|
def get_awaited_path_from_prefix(prefix, path): |
|
"""Guess the correct path to pass to the SHFileOperationW() call. |
|
The long-path prefix must be removed, so we should take care of |
|
different long-path prefixes. |
|
""" |
|
if prefix == "\\\\?\\UNC": |
|
|
|
|
|
return "\\" + path[len(prefix) :] |
|
return path[len(prefix) :] |
|
|
|
|
|
def get_short_path_name(long_name): |
|
prefix, long_path = prefix_and_path(long_name) |
|
buf_size = GetShortPathNameW(long_path, None, 0) |
|
|
|
|
|
if not buf_size: |
|
err_no = GetLastError() |
|
raise WindowsError(err_no, FormatError(err_no), long_path) |
|
output = create_unicode_buffer(buf_size) |
|
GetShortPathNameW(long_path, output, buf_size) |
|
return get_awaited_path_from_prefix(prefix, output.value) |
|
|
|
|
|
def send2trash(paths): |
|
paths = preprocess_paths(paths) |
|
if not paths: |
|
return |
|
|
|
paths = [text_type(path, "mbcs") if not isinstance(path, text_type) else path for path in paths] |
|
|
|
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] |
|
|
|
paths = [get_short_path_name(path) for path in paths] |
|
fileop = SHFILEOPSTRUCTW() |
|
fileop.hwnd = 0 |
|
fileop.wFunc = FO_DELETE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
buffer = create_unicode_buffer(" ".join(paths)) |
|
|
|
path_string = "\0".join(paths) |
|
buffer = create_unicode_buffer(path_string, len(buffer) + 1) |
|
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: |
|
error = convert_sh_file_opt_result(result) |
|
raise WindowsError(None, FormatError(error), paths, error) |
|
|