From 5865e582fd537fff530c13301e5229a7b4ed21c7 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 17 Nov 2016 10:11:32 +0100 Subject: [PATCH] Windows QPA/Open file dialog: Copy non-filesystem items With the introduction of the new IFileDialog interfaces in Qt 5, the open file dialog no longer was able to open items on MTP mounted devices. The Win32 API GetOpenFileName() used in Qt 4 would hide this by creating a local copy of the file in the INetCache folder. Add code to emulate the behavior in QWindowsNativeOpenFileDialog::dialogResult(). Task-number: QTBUG-57070 Change-Id: I88cccfbf9697585225356cc864df67c86a912c91 Reviewed-by: Joerg Bornemann --- .../windows/qwindowsdialoghelpers.cpp | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index c00be0c800d..63f78cfa637 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include @@ -591,6 +592,8 @@ public: // Supports IStream bool canStream() const { return (m_attributes & SFGAO_STREAM) != 0; } + bool copyData(QIODevice *out); + static IShellItems itemsFromItemArray(IShellItemArray *items); #ifndef QT_NO_DEBUG_STREAM @@ -681,6 +684,29 @@ QWindowsShellItem::IShellItems QWindowsShellItem::itemsFromItemArray(IShellItemA return result; } +bool QWindowsShellItem::copyData(QIODevice *out) +{ + if (!canCopy() || !canStream()) + return false; + IStream *istream = nullptr; + HRESULT hr = m_item->BindToHandler(NULL, BHID_Stream, IID_PPV_ARGS(&istream)); + if (FAILED(hr)) + return false; + enum : ULONG { bufSize = 102400 }; + char buffer[bufSize]; + ULONG bytesRead; + forever { + bytesRead = 0; + hr = istream->Read(buffer, bufSize, &bytesRead); // S_FALSE: EOF reached + if ((hr == S_OK || hr == S_FALSE) && bytesRead) + out->write(buffer, bytesRead); + else + break; + } + istream->Release(); + return hr == S_OK || hr == S_FALSE; +} + // Helper for "Libraries": collections of folders appearing from Windows 7 // on, visible in the file dialogs. @@ -1345,18 +1371,59 @@ private: { return static_cast(fileDialog()); } }; +// Helpers for managing a list of temporary copies of items with no +// file system representation (SFGAO_FILESYSTEM unset, for example devices +// using MTP) returned by IFileOpenDialog. This emulates the behavior +// of the Win32 API GetOpenFileName() used in Qt 4 (QTBUG-57070). + +Q_GLOBAL_STATIC(QStringList, temporaryItemCopies) + +static void cleanupTemporaryItemCopies() +{ + for (const QString &file : qAsConst(*temporaryItemCopies())) + QFile::remove(file); +} + +static QString createTemporaryItemCopy(QWindowsShellItem &qItem) +{ + if (!qItem.canCopy() || !qItem.canStream()) + return QString(); + QString pattern = qItem.normalDisplay(); + const int lastDot = pattern.lastIndexOf(QLatin1Char('.')); + const QString placeHolder = QStringLiteral("_XXXXXX"); + if (lastDot >= 0) + pattern.insert(lastDot, placeHolder); + else + pattern.append(placeHolder); + + QTemporaryFile targetFile(QDir::tempPath() + QLatin1Char('/') + pattern); + targetFile.setAutoRemove(false); + if (!targetFile.open() || !qItem.copyData(&targetFile)) + return QString(); + const QString result = targetFile.fileName(); + if (temporaryItemCopies()->isEmpty()) + qAddPostRoutine(cleanupTemporaryItemCopies); + temporaryItemCopies()->append(result); + return result; +} + QList QWindowsNativeOpenFileDialog::dialogResult() const { QList result; IShellItemArray *items = 0; if (SUCCEEDED(openFileDialog()->GetResults(&items)) && items) { for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) { - const QWindowsShellItem qItem(item); - const QUrl url = qItem.url(); - if (url.isValid()) - result.append(url); - else - qWarning().nospace() << __FUNCTION__<< ": Unable to obtain URL of " << qItem; + QWindowsShellItem qItem(item); + const QString path = qItem.path(); + if (path.isEmpty()) { + const QString temporaryCopy = createTemporaryItemCopy(qItem); + if (temporaryCopy.isEmpty()) + qWarning() << "Unable to create a local copy of" << qItem; + else + result.append(QUrl::fromLocalFile(temporaryCopy)); + } else { + result.append(qItem.url()); + } } } return result;