From 9844394258895ab455f5d6a28b39b6b000b3fe99 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Jun 2019 05:42:03 +0530 Subject: [PATCH] Windows: Dont use a deprecated API for moving to trash --- src/calibre/utils/recycle_bin.py | 8 +- src/calibre/utils/windows/winutil.c | 5 + src/calibre/utils/windows/winutilpp.cpp | 116 +++++++++++++++++++++++- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/calibre/utils/recycle_bin.py b/src/calibre/utils/recycle_bin.py index 65cc46a49c..9354193de5 100644 --- a/src/calibre/utils/recycle_bin.py +++ b/src/calibre/utils/recycle_bin.py @@ -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) diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 6315ab51cb..a2b564d29f 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -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} }; diff --git a/src/calibre/utils/windows/winutilpp.cpp b/src/calibre/utils/windows/winutilpp.cpp index 1ba43e84e9..edde6a795f 100644 --- a/src/calibre/utils/windows/winutilpp.cpp +++ b/src/calibre/utils/windows/winutilpp.cpp @@ -13,12 +13,65 @@ #include #include // for CComPtr #include +#include -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(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 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 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 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; +} + }