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
parent
5c520f4b0a
commit
e79979ba40
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue