mirror of
https://github.com/arsenetar/send2trash.git
synced 2024-11-13 02:59:02 +00:00
fail if not recyclable
This commit is contained in:
parent
0a48c26f68
commit
5e9d56bdcb
@ -6,6 +6,10 @@ from win32com.shell import shell, shellcon
|
||||
from win32com.server.policy import DesignatedWrapPolicy
|
||||
|
||||
|
||||
class E_Fail(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FileOperationProgressSink(DesignatedWrapPolicy):
|
||||
_com_interfaces_ = [shell.IID_IFileOperationProgressSink]
|
||||
_public_methods_ = [
|
||||
@ -29,18 +33,30 @@ class FileOperationProgressSink(DesignatedWrapPolicy):
|
||||
|
||||
def __init__(self):
|
||||
self._wrap_(self)
|
||||
self.newItem = None
|
||||
self.errors = []
|
||||
|
||||
def PreDeleteItem(self, flags, item):
|
||||
# Can detect cases where to stop via flags and condition below, however the operation
|
||||
# does not actual stop, we can resort to raising an exception as that does stop things
|
||||
# but that may need some additional considerations before implementing.
|
||||
return 0 if flags & shellcon.TSF_DELETE_RECYCLE_IF_POSSIBLE else 0x80004005 # S_OK, or E_FAIL
|
||||
# If TSF_DELETE_RECYCLE_IF_POSSIBLE is not set the file would not be moved to trash.
|
||||
# Usually the code would have to return S_OK or E_FAIL to signal an abort to the file sink,
|
||||
# however pywin32 doesn't use the return value of these callback methods [1], so we have to resort
|
||||
# to raising an exception as that does stop things.
|
||||
# [1] https://github.com/mhammond/pywin32/blob/1d29e4a4f317be9acbef9d5c5c5787269eacb040/com/win32com/src/PyGatewayBase.cpp#L757
|
||||
|
||||
name = item.GetDisplayName(shellcon.SHGDN_FORPARSING)
|
||||
will_recycle = flags & shellcon.TSF_DELETE_RECYCLE_IF_POSSIBLE
|
||||
if not will_recycle:
|
||||
raise E_Fail(f"File would be deleted permanently: {name}")
|
||||
|
||||
return None # HR cannot be returned here
|
||||
|
||||
def PostDeleteItem(self, flags, item, hr_delete, newly_created):
|
||||
if newly_created:
|
||||
self.newItem = newly_created.GetDisplayName(shellcon.SHGDN_FORPARSING)
|
||||
if hr_delete < 0:
|
||||
name = item.GetDisplayName(shellcon.SHGDN_FORPARSING)
|
||||
self.errors.append((name, hr_delete))
|
||||
|
||||
return None # HR cannot be returned here
|
||||
|
||||
|
||||
def create_sink():
|
||||
return pythoncom.WrapObject(FileOperationProgressSink(), shell.IID_IFileOperationProgressSink)
|
||||
pysink = FileOperationProgressSink()
|
||||
return pysink, pythoncom.WrapObject(pysink, shell.IID_IFileOperationProgressSink)
|
||||
|
@ -13,6 +13,20 @@ import pythoncom
|
||||
import pywintypes
|
||||
from win32com.shell import shell, shellcon
|
||||
from send2trash.win.IFileOperationProgressSink import create_sink
|
||||
from win32api import FormatMessage
|
||||
from winerror import ERROR_SHARING_VIOLATION
|
||||
|
||||
winerrormap = {
|
||||
shellcon.COPYENGINE_E_SHARING_VIOLATION_SRC: ERROR_SHARING_VIOLATION,
|
||||
}
|
||||
|
||||
|
||||
def win_exception(winerror, filename):
|
||||
# see `PyErr_SetExcFromWindowsErrWithFilenameObjects`
|
||||
msg = FormatMessage(winerror).rstrip(
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ."
|
||||
)
|
||||
return WindowsError(None, msg, filename, winerror)
|
||||
|
||||
|
||||
def send2trash(paths):
|
||||
@ -47,7 +61,7 @@ def send2trash(paths):
|
||||
# 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
|
||||
sink = create_sink()
|
||||
pysink, sink = create_sink()
|
||||
try:
|
||||
for path in paths:
|
||||
item = shell.SHCreateItemFromParsingName(path, None, shell.IID_IShellItem)
|
||||
@ -55,12 +69,16 @@ def send2trash(paths):
|
||||
result = fileop.PerformOperations()
|
||||
aborted = fileop.GetAnyOperationsAborted()
|
||||
# if non-zero result or aborted throw an exception
|
||||
assert not pysink.errors, pysink.errors
|
||||
if result or aborted:
|
||||
raise OSError(None, None, paths, result)
|
||||
except pywintypes.com_error as error:
|
||||
except pywintypes.com_error:
|
||||
assert len(pysink.errors) == 1, pysink.errors
|
||||
# convert to standard OS error, allows other code to get a
|
||||
# normal errno
|
||||
raise OSError(None, error.strerror, path, error.hresult)
|
||||
path, hr = pysink.errors[0]
|
||||
hr = winerrormap.get(hr + 2**32, hr)
|
||||
raise win_exception(hr, path)
|
||||
finally:
|
||||
# Need to make sure we call this once fore every init
|
||||
pythoncom.CoUninitialize()
|
||||
|
Loading…
Reference in New Issue
Block a user