On Windows 7, fall back to SHFileOperation to avoid confirmation dialogs

Moving a file to the trash should preferably done via IFileOperation.
However, the implementation on Windows 7 ignores the operation flags
that request the shell not to show any confirmation dialogs or other UI
elements.

SHFileOperation is an old API that doesn't show any UI, but has the
limitation that it doesn't report the location of the file in the trash
after the move. So an application cannot restore the file, but the user
can do so via Explorer.

Overall, the better compromise is to not have dialogs at the expense of
not being able to report the new path. This allows us to run the unit
test on Windows 7 as well.

Change-Id: Ib8e651a69e2c6750f668b52d2a70925d156cc8ae
Fixes: QTBUG-81927
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
bb10
Volker Hilsheimer 2020-02-06 10:17:52 +01:00
parent 5c520f4b0a
commit e79979ba40
2 changed files with 71 additions and 68 deletions

View File

@ -1546,73 +1546,79 @@ bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
// we need the "display name" of the file, so can't use nativeFilePath
const QString sourcePath = QDir::toNativeSeparators(source.filePath());
# if defined(__IFileOperation_INTERFACE_DEFINED__)
CoInitialize(NULL);
IFileOperation *pfo = nullptr;
IShellItem *deleteItem = nullptr;
FileOperationProgressSink *sink = nullptr;
HRESULT hres = E_FAIL;
auto coUninitialize = qScopeGuard([&](){
if (sink)
sink->Release();
if (deleteItem)
deleteItem->Release();
if (pfo)
pfo->Release();
CoUninitialize();
if (!SUCCEEDED(hres))
error = QSystemError(hres, QSystemError::NativeError);
});
hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
if (!pfo)
return false;
pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION
| FOF_SILENT | FOF_NOERRORUI);
hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()),
nullptr, IID_PPV_ARGS(&deleteItem));
if (!deleteItem)
return false;
sink = new FileOperationProgressSink;
hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink));
if (!SUCCEEDED(hres))
return false;
hres = pfo->PerformOperations();
if (!SUCCEEDED(hres))
return false;
newLocation = QFileSystemEntry(sink->targetPath);
# else // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation
// double null termination needed, so can't use QString::utf16
QVarLengthArray<wchar_t, MAX_PATH + 1> winFile(sourcePath.length() + 2);
sourcePath.toWCharArray(winFile.data());
winFile[sourcePath.length()] = wchar_t{};
winFile[sourcePath.length() + 1] = wchar_t{};
SHFILEOPSTRUCTW operation;
operation.hwnd = nullptr;
operation.wFunc = FO_DELETE;
operation.pFrom = winFile.constData();
operation.pTo = nullptr;
operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
operation.fAnyOperationsAborted = FALSE;
operation.hNameMappings = nullptr;
operation.lpszProgressTitle = nullptr;
int result = SHFileOperation(&operation);
if (result != 0) {
error = QSystemError(result, QSystemError::NativeError);
return false;
}
/*
This implementation doesn't let us know where the file ended up, even if
we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as
FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed.
Windows 7 insists on showing confirmation dialogs and ignores the respective
flags set on IFileOperation. Fall back to SHFileOperation, even if it doesn't
give us the new location of the file.
*/
Q_UNUSED(newLocation);
# endif // IFileOperation fallback
if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) {
# if defined(__IFileOperation_INTERFACE_DEFINED__)
CoInitialize(NULL);
IFileOperation *pfo = nullptr;
IShellItem *deleteItem = nullptr;
FileOperationProgressSink *sink = nullptr;
HRESULT hres = E_FAIL;
auto coUninitialize = qScopeGuard([&](){
if (sink)
sink->Release();
if (deleteItem)
deleteItem->Release();
if (pfo)
pfo->Release();
CoUninitialize();
if (!SUCCEEDED(hres))
error = QSystemError(hres, QSystemError::NativeError);
});
hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
if (!pfo)
return false;
pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION
| FOF_SILENT | FOF_NOERRORUI);
hres = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t*>(sourcePath.utf16()),
nullptr, IID_PPV_ARGS(&deleteItem));
if (!deleteItem)
return false;
sink = new FileOperationProgressSink;
hres = pfo->DeleteItem(deleteItem, static_cast<IFileOperationProgressSink*>(sink));
if (!SUCCEEDED(hres))
return false;
hres = pfo->PerformOperations();
if (!SUCCEEDED(hres))
return false;
newLocation = QFileSystemEntry(sink->targetPath);
# endif // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation
} else {
// double null termination needed, so can't use QString::utf16
QVarLengthArray<wchar_t, MAX_PATH + 1> winFile(sourcePath.length() + 2);
sourcePath.toWCharArray(winFile.data());
winFile[sourcePath.length()] = wchar_t{};
winFile[sourcePath.length() + 1] = wchar_t{};
SHFILEOPSTRUCTW operation;
operation.hwnd = nullptr;
operation.wFunc = FO_DELETE;
operation.pFrom = winFile.constData();
operation.pTo = nullptr;
operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
operation.fAnyOperationsAborted = FALSE;
operation.hNameMappings = nullptr;
operation.lpszProgressTitle = nullptr;
int result = SHFileOperation(&operation);
if (result != 0) {
error = QSystemError(result, QSystemError::NativeError);
return false;
}
/*
This implementation doesn't let us know where the file ended up, even if
we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as
FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed.
*/
Q_UNUSED(newLocation);
}
return true;
#else // Q_OS_WINRT

View File

@ -3681,9 +3681,6 @@ void tst_QFile::moveToTrash_data()
QTest::addColumn<bool>("create");
QTest::addColumn<bool>("result");
if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7)
QSKIP("Windows 7 insists on showing a confirmation dialog", SkipAll);
// success cases
{
QTemporaryFile temp;