summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qfilesystemengine_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qfilesystemengine_win.cpp')
-rw-r--r--src/corelib/io/qfilesystemengine_win.cpp303
1 files changed, 248 insertions, 55 deletions
diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp
index ae29190848..6ad123f3b6 100644
--- a/src/corelib/io/qfilesystemengine_win.cpp
+++ b/src/corelib/io/qfilesystemengine_win.cpp
@@ -41,6 +41,7 @@
#include "qoperatingsystemversion.h"
#include "qplatformdefs.h"
#include "qsysinfo.h"
+#include "qscopeguard.h"
#include "private/qabstractfileengine_p.h"
#include "private/qfsfileengine_p.h"
#include <private/qsystemlibrary_p.h>
@@ -59,6 +60,8 @@
#include <objbase.h>
#ifndef Q_OS_WINRT
# include <shlobj.h>
+# include <shobjidl.h>
+# include <shellapi.h>
# include <lm.h>
# include <accctrl.h>
#endif
@@ -391,7 +394,7 @@ static QString readLink(const QFileSystemEntry &link)
static bool uncShareExists(const QString &server)
{
// This code assumes the UNC path is always like \\?\UNC\server...
- const QVector<QStringRef> parts = server.splitRef(QLatin1Char('\\'), QString::SkipEmptyParts);
+ const QVector<QStringRef> parts = server.splitRef(QLatin1Char('\\'), Qt::SkipEmptyParts);
if (parts.count() >= 3) {
QStringList shares;
if (QFileSystemEngine::uncListSharesOnServer(QLatin1String("\\\\") + parts.at(2), &shares))
@@ -422,6 +425,104 @@ static inline bool getFindData(QString path, WIN32_FIND_DATA &findData)
return false;
}
+#if defined(__IFileOperation_INTERFACE_DEFINED__)
+class FileOperationProgressSink : public IFileOperationProgressSink
+{
+public:
+ FileOperationProgressSink()
+ : ref(1)
+ {}
+ virtual ~FileOperationProgressSink() {}
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return ++ref;
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ if (--ref == 0) {
+ delete this;
+ return 0;
+ }
+ return ref;
+ }
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
+ {
+ if (!ppvObject)
+ return E_POINTER;
+
+ *ppvObject = nullptr;
+
+ if (iid == __uuidof(IUnknown)) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else if (iid == __uuidof(IFileOperationProgressSink)) {
+ *ppvObject = static_cast<IFileOperationProgressSink*>(this);
+ }
+
+ if (*ppvObject) {
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ HRESULT STDMETHODCALLTYPE StartOperations()
+ { 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 S_OK; }
+ HRESULT STDMETHODCALLTYPE PreMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PostMoveItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT,
+ IShellItem *)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PreCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR )
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PostCopyItem(DWORD, IShellItem *, IShellItem *, LPCWSTR, HRESULT,
+ IShellItem *)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, IShellItem *)
+ {
+ // stop the operation if the file will be deleted rather than trashed
+ return (dwFlags & TSF_DELETE_RECYCLE_IF_POSSIBLE) ? S_OK : E_FAIL;
+ }
+ HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD /* dwFlags */, IShellItem * /* psiItem */,
+ HRESULT /* hrDelete */, IShellItem *psiNewlyCreated)
+ {
+ if (psiNewlyCreated) {
+ wchar_t *pszName = nullptr;
+ psiNewlyCreated->GetDisplayName(SIGDN_FILESYSPATH, &pszName);
+ if (pszName) {
+ targetPath = QString::fromWCharArray(pszName);
+ CoTaskMemFree(pszName);
+ }
+ }
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE PreNewItem(DWORD, IShellItem *, LPCWSTR)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PostNewItem(DWORD, IShellItem *, LPCWSTR, LPCWSTR, DWORD, HRESULT,
+ IShellItem *)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE UpdateProgress(UINT,UINT)
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE ResetTimer()
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE PauseTimer()
+ { return S_OK; }
+ HRESULT STDMETHODCALLTYPE ResumeTimer()
+ { return S_OK; }
+
+ QString targetPath;
+private:
+ ULONG ref;
+};
+#endif
+
bool QFileSystemEngine::uncListSharesOnServer(const QString &server, QStringList *list)
{
DWORD res = ERROR_NOT_SUPPORTED;
@@ -1123,67 +1224,62 @@ static bool isDirPath(const QString &dirPath, bool *existed)
return fileAttrib & FILE_ATTRIBUTE_DIRECTORY;
}
+// NOTE: if \a shouldMkdirFirst is false, we assume the caller did try to mkdir
+// before calling this function.
+static bool createDirectoryWithParents(const QString &nativeName, bool shouldMkdirFirst = true)
+{
+ const auto isUNCRoot = [](const QString &nativeName) {
+ return nativeName.startsWith(QLatin1String("\\\\")) && nativeName.count(QDir::separator()) <= 3;
+ };
+ const auto isDriveName = [](const QString &nativeName) {
+ return nativeName.size() == 2 && nativeName.at(1) == QLatin1Char(':');
+ };
+ const auto isDir = [](const QString &nativeName) {
+ bool exists = false;
+ return isDirPath(nativeName, &exists) && exists;
+ };
+ // Do not try to mkdir a UNC root path or a drive letter.
+ if (isUNCRoot(nativeName) || isDriveName(nativeName))
+ return false;
+
+ if (shouldMkdirFirst) {
+ if (mkDir(nativeName))
+ return true;
+ }
+
+ const int backSlash = nativeName.lastIndexOf(QDir::separator());
+ if (backSlash < 1)
+ return false;
+
+ const QString parentNativeName = nativeName.left(backSlash);
+ if (!createDirectoryWithParents(parentNativeName))
+ return false;
+
+ // try again
+ if (mkDir(nativeName))
+ return true;
+ return isDir(nativeName);
+}
+
//static
bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents)
{
QString dirName = entry.filePath();
Q_CHECK_FILE_NAME(dirName, false);
- if (createParents) {
- dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
- // We spefically search for / so \ would break it..
- int oldslash = -1;
- if (dirName.startsWith(QLatin1String("\\\\"))) {
- // Don't try to create the root path of a UNC path;
- // CreateDirectory() will just return ERROR_INVALID_NAME.
- for (int i = 0; i < dirName.size(); ++i) {
- if (dirName.at(i) != QDir::separator()) {
- oldslash = i;
- break;
- }
- }
- if (oldslash != -1)
- oldslash = dirName.indexOf(QDir::separator(), oldslash);
- } else if (dirName.size() > 2
- && dirName.at(1) == QLatin1Char(':')) {
- // Don't try to call mkdir with just a drive letter
- oldslash = 2;
- }
- for (int slash=0; slash != -1; oldslash = slash) {
- slash = dirName.indexOf(QDir::separator(), oldslash+1);
- if (slash == -1) {
- if (oldslash == dirName.length())
- break;
- slash = dirName.length();
- }
- if (slash) {
- DWORD lastError;
- QString chunk = dirName.left(slash);
- if (!mkDir(chunk, &lastError)) {
- if (lastError == ERROR_ALREADY_EXISTS || lastError == ERROR_ACCESS_DENIED) {
- bool existed = false;
- if (isDirPath(chunk, &existed) && existed)
- continue;
-#ifdef Q_OS_WINRT
- static QThreadStorage<QString> dataLocation;
- if (!dataLocation.hasLocalData())
- dataLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)));
- static QThreadStorage<QString> tempLocation;
- if (!tempLocation.hasLocalData())
- tempLocation.setLocalData(QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::TempLocation)));
- // We try to create something outside the sandbox, which is forbidden
- // However we could still try to pass into the sandbox
- if (dataLocation.localData().startsWith(chunk) || tempLocation.localData().startsWith(chunk))
- continue;
-#endif
- }
- return false;
- }
- }
- }
+ dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName));
+
+ // try to mkdir this directory
+ DWORD lastError;
+ if (mkDir(dirName, &lastError))
return true;
- }
- return mkDir(entry.filePath());
+ // mkpath should return true, if the directory already exists, mkdir false.
+ if (!createParents)
+ return false;
+ if (lastError == ERROR_ALREADY_EXISTS)
+ return isDirPath(dirName, nullptr);
+
+ return createDirectoryWithParents(dirName, false);
}
//static
@@ -1436,6 +1532,103 @@ bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &
return ret;
}
+/*
+ If possible, we use the IFileOperation implementation, which allows us to determine
+ the location of the object in the trash.
+ If not (likely on mingw), we fall back to the old API, which won't allow us to know
+ that.
+*/
+//static
+bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source,
+ QFileSystemEntry &newLocation, QSystemError &error)
+{
+#ifndef Q_OS_WINRT
+ // we need the "display name" of the file, so can't use nativeFilePath
+ const QString sourcePath = QDir::toNativeSeparators(source.filePath());
+
+ /*
+ 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.
+ */
+ 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
+ Q_UNUSED(source);
+ Q_UNUSED(newLocation);
+ Q_UNUSED(error);
+ return false;
+#endif
+}
+
//static
bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error,
QFileSystemMetaData *data)