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 <joerg.bornemann@qt.io>
This commit is contained in:
Friedemann Kleint 2016-11-17 10:11:32 +01:00
parent 1d2a32d1c0
commit 5865e582fd

View File

@ -66,6 +66,7 @@
#include <QtCore/QMutexLocker>
#include <QtCore/QUuid>
#include <QtCore/QRegularExpression>
#include <QtCore/QTemporaryFile>
#include <QtCore/private/qsystemlibrary_p.h>
#include <algorithm>
@ -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<IFileOpenDialog *>(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<QUrl> QWindowsNativeOpenFileDialog::dialogResult() const
{
QList<QUrl> 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;