Windows: Dont use a deprecated API for moving to trash

This commit is contained in:
Kovid Goyal 2019-06-12 05:42:03 +05:30
parent 7a048fe361
commit 9844394258
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 119 additions and 10 deletions

View File

@ -11,6 +11,7 @@ import os, shutil, time, sys
from calibre import isbytestring
from calibre.constants import (iswindows, isosx, plugins, filesystem_encoding,
islinux)
from polyglot.builtins import unicode_type
recycle = None
@ -27,12 +28,7 @@ if iswindows:
recycler = start_pipe_worker('from calibre.utils.recycle_bin import recycler_main; recycler_main()')
def recycle_path(path):
from win32com.shell import shell, shellcon
flags = (shellcon.FOF_ALLOWUNDO | shellcon.FOF_NOCONFIRMATION | shellcon.FOF_NOCONFIRMMKDIR | shellcon.FOF_NOERRORUI |
shellcon.FOF_SILENT | shellcon.FOF_RENAMEONCOLLISION)
retcode, aborted = shell.SHFileOperation((0, shellcon.FO_DELETE, path, None, flags, None, None))
if retcode != 0 or aborted:
raise RuntimeError('Failed to delete: %r with error code: %d' % (path, retcode))
plugins['winutil'][0].move_to_trash(unicode_type(path))
def recycler_main():
stdin = getattr(sys.stdin, 'buffer', sys.stdin)

View File

@ -379,6 +379,7 @@ extern PyObject *winutil_add_to_recent_docs(PyObject *self, PyObject *args);
extern PyObject *winutil_file_association(PyObject *self, PyObject *args);
extern PyObject *winutil_friendly_name(PyObject *self, PyObject *args);
extern PyObject *winutil_notify_associations_changed(PyObject *self, PyObject *args);
extern PyObject *winutil_move_to_trash(PyObject *self, PyObject *args);
static PyMethodDef winutil_methods[] = {
{"special_folder_path", winutil_folder_path, METH_VARARGS,
@ -462,6 +463,10 @@ be a unicode string. Returns unicode strings."
"notify_associations_changed()\n\nNotify the OS that file associations have changed"
},
{"move_to_trash", (PyCFunction)winutil_move_to_trash, METH_VARARGS,
"move_to_trash()\n\nMove the specified path to trash"
},
{NULL, NULL, 0, NULL}
};

View File

@ -13,12 +13,65 @@
#include <shlwapi.h>
#include <atlbase.h> // for CComPtr
#include <Python.h>
#include <versionhelpers.h>
class wchar_raii {
class DeleteFileProgressSink : public IFileOperationProgressSink { // {{{
public:
DeleteFileProgressSink() : m_cRef(0) {}
private:
ULONG STDMETHODCALLTYPE AddRef(void) { InterlockedIncrement(&m_cRef); return m_cRef; }
ULONG STDMETHODCALLTYPE Release(void) {
ULONG ulRefCount = InterlockedDecrement(&m_cRef);
if (0 == m_cRef) delete this;
return ulRefCount;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObj) {
if (!ppvObj) return E_INVALIDARG;
*ppvObj = nullptr;
if (riid == IID_IUnknown || riid == IID_IFileOperationProgressSink) {
// Increment the reference count and return the pointer.
*ppvObj = reinterpret_cast<IUnknown*>(this);
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE StartOperations(void) { return S_OK; }
HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT) { return S_OK; }
HRESULT STDMETHODCALLTYPE PreRenameItem(
DWORD, IShellItem*, LPCWSTR) { return S_OK; }
HRESULT STDMETHODCALLTYPE PostRenameItem(
DWORD, IShellItem*, LPCWSTR, HRESULT, IShellItem*) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PreMoveItem(
DWORD, IShellItem*, IShellItem*, LPCWSTR) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PostMoveItem(
DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PreCopyItem(
DWORD, IShellItem*, IShellItem*, LPCWSTR) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PostCopyItem(
DWORD, IShellItem*, IShellItem*, LPCWSTR, HRESULT, IShellItem*) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, IShellItem*) {
if (!(dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE)) return E_ABORT;
return S_OK;
}
HRESULT STDMETHODCALLTYPE PostDeleteItem(
DWORD, IShellItem*, HRESULT, IShellItem*) { return S_OK; }
HRESULT STDMETHODCALLTYPE PreNewItem(
DWORD, IShellItem*, LPCWSTR) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE PostNewItem(
DWORD, IShellItem*, LPCWSTR, LPCWSTR, DWORD, HRESULT, IShellItem*) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE UpdateProgress(UINT, UINT) { return S_OK; }
HRESULT STDMETHODCALLTYPE ResetTimer(void) { return S_OK; }
HRESULT STDMETHODCALLTYPE PauseTimer(void) { return S_OK; }
HRESULT STDMETHODCALLTYPE ResumeTimer(void) { return S_OK; }
ULONG m_cRef;
}; // }}}
class wchar_raii { // {{{
private:
wchar_t **handle;
// copy and assignment not implemented; prevent their use by
// declaring private.
wchar_raii( const wchar_raii & ) ;
wchar_raii & operator=( const wchar_raii & ) ;
@ -34,7 +87,18 @@ class wchar_raii {
wchar_t *ptr() { return *handle; }
void set_ptr(wchar_t **val) { handle = val; }
};
}; // }}}
class scoped_com_initializer { // {{{
public:
scoped_com_initializer() : m_succeded(false) { if (SUCCEEDED(CoInitialize(NULL))) m_succeded = true; }
~scoped_com_initializer() { CoUninitialize(); }
bool succeded() { return m_succeded; }
private:
bool m_succeded;
scoped_com_initializer( const scoped_com_initializer & ) ;
scoped_com_initializer & operator=( const scoped_com_initializer & ) ;
}; // }}}
static inline int
py_to_wchar(PyObject *obj, wchar_raii *output) {
@ -107,4 +171,48 @@ winutil_notify_associations_changed(PyObject *self, PyObject *args) {
Py_RETURN_NONE;
}
PyObject *
winutil_move_to_trash(PyObject *self, PyObject *args) {
wchar_raii path;
if (!PyArg_ParseTuple(args, "O&", py_to_wchar, &path)) return NULL;
scoped_com_initializer com;
if (!com.succeded()) { PyErr_SetString(PyExc_OSError, "Failed to initialize COM"); return NULL; }
CComPtr<IFileOperation> pfo;
if (FAILED(CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)))) {
PyErr_SetString(PyExc_OSError, "Failed to create IFileOperation instance");
return NULL;
}
DWORD flags = FOF_NO_UI | FOF_NOERRORUI | FOF_SILENT;
if (IsWindows8OrGreater()) {
flags |= FOFX_ADDUNDORECORD | FOFX_RECYCLEONDELETE;
} else {
flags |= FOF_ALLOWUNDO;
}
if (FAILED(pfo->SetOperationFlags(flags))) {
PyErr_SetString(PyExc_OSError, "Failed to set operation flags");
return NULL;
}
CComPtr<IShellItem> delete_item;
if (FAILED(SHCreateItemFromParsingName(path.ptr(), NULL, IID_PPV_ARGS(&delete_item)))) {
PyErr_SetString(PyExc_OSError, "Failed to create shell item");
return NULL;
}
CComPtr<IFileOperationProgressSink> delete_sink(new DeleteFileProgressSink);
if (FAILED(pfo->DeleteItem(delete_item, delete_sink))) {
PyErr_SetString(PyExc_OSError, "Failed to delete item");
return NULL;
}
if (FAILED(pfo->PerformOperations())) {
PyErr_SetString(PyExc_OSError, "Failed to perform delete operation");
return NULL;
}
Py_RETURN_NONE;
}
}