blob: 962e4ab938a8dfc00b1b74f4b14113266b418ee5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwinrtfileengine.h"
#include <QtCore/QDateTime>
#include <QtCore/QCoreApplication>
#include <QtCore/QHash>
#include <QtCore/qfunctions_winrt.h>
#include <QtCore/private/qfsfileengine_p.h>
#include <wrl.h>
#include <windows.storage.h>
#include <robuffer.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Streams;
typedef IAsyncOperationCompletedHandler<IRandomAccessStream *> StreamCompletedHandler;
typedef IAsyncOperationWithProgressCompletedHandler<IBuffer *, UINT32> StreamReadCompletedHandler;
QT_BEGIN_NAMESPACE
#define RETURN_AND_SET_ERROR_IF_FAILED(error, ret) \
setError(error, qt_error_string(hr)); \
if (FAILED(hr)) \
return ret;
Q_GLOBAL_STATIC(QWinRTFileEngineHandler, handlerInstance)
class QWinRTFileEngineHandlerPrivate
{
public:
QHash<QString, ComPtr<IStorageItem>> files;
};
class QWinRTFileEnginePrivate
{
public:
QWinRTFileEnginePrivate(const QString &fileName, IStorageItem *file)
: fileName(fileName), file(file), openMode(QIODevice::NotOpen)
{
HRESULT hr;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
IID_PPV_ARGS(&bufferFactory));
Q_ASSERT_SUCCEEDED(hr);
lastSeparator = fileName.size() - 1;
for (int i = lastSeparator; i >= 0; --i) {
if (fileName.at(i).unicode() == '/' || fileName.at(i).unicode() == '\\') {
lastSeparator = i;
break;
}
}
firstDot = fileName.size();
for (int i = lastSeparator; i < fileName.size(); ++i) {
if (fileName.at(i).unicode() == '.') {
firstDot = i;
break;
}
}
}
ComPtr<IBufferFactory> bufferFactory;
QString fileName;
int lastSeparator;
int firstDot;
ComPtr<IStorageItem> file;
ComPtr<IRandomAccessStream> stream;
QIODevice::OpenMode openMode;
qint64 pos;
private:
QWinRTFileEngineHandler *q_ptr;
Q_DECLARE_PUBLIC(QWinRTFileEngineHandler)
};
QWinRTFileEngineHandler::QWinRTFileEngineHandler()
: d_ptr(new QWinRTFileEngineHandlerPrivate)
{
}
void QWinRTFileEngineHandler::registerFile(const QString &fileName, IStorageItem *file)
{
handlerInstance->d_func()->files.insert(QDir::cleanPath(fileName), file);
}
IStorageItem *QWinRTFileEngineHandler::registeredFile(const QString &fileName)
{
return handlerInstance->d_func()->files.value(fileName).Get();
}
QAbstractFileEngine *QWinRTFileEngineHandler::create(const QString &fileName) const
{
Q_D(const QWinRTFileEngineHandler);
QHash<QString, ComPtr<IStorageItem>>::const_iterator file = d->files.find(fileName);
if (file != d->files.end())
return new QWinRTFileEngine(fileName, file.value().Get());
return nullptr;
}
static HRESULT getDestinationFolder(const QString &fileName, const QString &newFileName,
IStorageItem *file, IStorageFolder **folder)
{
HRESULT hr;
ComPtr<IAsyncOperation<StorageFolder *>> op;
QFileInfo newFileInfo(newFileName);
QFileInfo fileInfo(fileName);
if (fileInfo.dir() == newFileInfo.dir()) {
ComPtr<IStorageItem2> item;
hr = file->QueryInterface(IID_PPV_ARGS(&item));
Q_ASSERT_SUCCEEDED(hr);
hr = item->GetParentAsync(&op);
} else {
ComPtr<IStorageFolderStatics> folderFactory;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_StorageFolder).Get(),
IID_PPV_ARGS(&folderFactory));
Q_ASSERT_SUCCEEDED(hr);
const QString newFilePath = QDir::toNativeSeparators(newFileInfo.absolutePath());
HStringReference nativeNewFilePath(reinterpret_cast<LPCWSTR>(newFilePath.utf16()),
uint(newFilePath.length()));
hr = folderFactory->GetFolderFromPathAsync(nativeNewFilePath.Get(), &op);
}
if (FAILED(hr))
return hr;
return QWinRTFunctions::await(op, folder);
}
QWinRTFileEngine::QWinRTFileEngine(const QString &fileName, IStorageItem *file)
: d_ptr(new QWinRTFileEnginePrivate(fileName, file))
{
}
bool QWinRTFileEngine::open(QIODevice::OpenMode openMode)
{
Q_D(QWinRTFileEngine);
FileAccessMode fileAccessMode = (openMode & QIODevice::WriteOnly)
? FileAccessMode_ReadWrite : FileAccessMode_Read;
HRESULT hr;
ComPtr<IStorageFile> file;
hr = d->file.As(&file);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::OpenError, false);
ComPtr<IAsyncOperation<IRandomAccessStream *>> op;
hr = file->OpenAsync(fileAccessMode, &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::OpenError, false);
hr = QWinRTFunctions::await(op, d->stream.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::OpenError, false);
const ProcessOpenModeResult res = processOpenModeFlags(openMode);
if (!res.ok) {
setError(QFileDevice::OpenError, res.error);
return false;
}
d->openMode = res.openMode;
if (d->openMode & QIODevice::Truncate) {
if (!setSize(0)) {
close();
setError(QFileDevice::OpenError, QLatin1String("Could not truncate file"));
return false;
}
}
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::close()
{
Q_D(QWinRTFileEngine);
if (!d->stream)
return false;
ComPtr<IClosable> closable;
HRESULT hr = d->stream.As(&closable);
Q_ASSERT_SUCCEEDED(hr);
hr = closable->Close();
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::UnspecifiedError, false);
d->stream.Reset();
d->openMode = QIODevice::NotOpen;
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::flush()
{
Q_D(QWinRTFileEngine);
if (!d->stream)
return false;
if (!(d->openMode & QIODevice::WriteOnly))
return true;
ComPtr<IOutputStream> stream;
HRESULT hr = d->stream.As(&stream);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, false);
ComPtr<IAsyncOperation<bool>> flushOp;
hr = stream->FlushAsync(&flushOp);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, false);
boolean flushed;
hr = QWinRTFunctions::await(flushOp, &flushed);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, false);
return true;
}
qint64 QWinRTFileEngine::size() const
{
Q_D(const QWinRTFileEngine);
if (!d->stream)
return 0;
UINT64 size;
HRESULT hr;
hr = d->stream->get_Size(&size);
RETURN_IF_FAILED("Failed to get file size", return 0);
return qint64(size);
}
bool QWinRTFileEngine::setSize(qint64 size)
{
Q_D(QWinRTFileEngine);
if (!d->stream) {
setError(QFileDevice::ResizeError, QLatin1String("File must be open to be resized"));
return false;
}
if (size < 0) {
setError(QFileDevice::ResizeError, QLatin1String("File size cannot be negative"));
return false;
}
HRESULT hr = d->stream->put_Size(static_cast<quint64>(size));
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ResizeError, false);
if (!flush()) {
setError(QFileDevice::ResizeError, QLatin1String("Could not flush file"));
return false;
}
return true;
}
qint64 QWinRTFileEngine::pos() const
{
Q_D(const QWinRTFileEngine);
return d->pos;
}
bool QWinRTFileEngine::seek(qint64 pos)
{
Q_D(QWinRTFileEngine);
if (!d->stream)
return false;
HRESULT hr = d->stream->Seek(UINT64(pos));
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::PositionError, false);
d->pos = pos;
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::remove()
{
Q_D(QWinRTFileEngine);
ComPtr<IAsyncAction> op;
HRESULT hr = d->file->DeleteAsync(StorageDeleteOption_Default, &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RemoveError, false);
hr = QWinRTFunctions::await(op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RemoveError, false);
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::copy(const QString &newName)
{
Q_D(QWinRTFileEngine);
HRESULT hr;
ComPtr<IStorageFolder> destinationFolder;
hr = getDestinationFolder(d->fileName, newName, d->file.Get(), destinationFolder.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false);
ComPtr<IStorageFile> file;
hr = d->file.As(&file);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false);
const QString destinationName = QFileInfo(newName).fileName();
HStringReference nativeDestinationName(reinterpret_cast<LPCWSTR>(destinationName.utf16()),
uint(destinationName.length()));
ComPtr<IAsyncOperation<StorageFile *>> op;
hr = file->CopyOverloadDefaultOptions(destinationFolder.Get(), nativeDestinationName.Get(), &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false);
ComPtr<IStorageFile> newFile;
hr = QWinRTFunctions::await(op, newFile.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false);
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::rename(const QString &newName)
{
Q_D(QWinRTFileEngine);
HRESULT hr;
ComPtr<IStorageFolder> destinationFolder;
hr = getDestinationFolder(d->fileName, newName, d->file.Get(), destinationFolder.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RenameError, false);
const QString destinationName = QFileInfo(newName).fileName();
HStringReference nativeDestinationName(reinterpret_cast<LPCWSTR>(destinationName.utf16()),
uint(destinationName.length()));
ComPtr<IAsyncAction> op;
hr = d->file->RenameAsyncOverloadDefaultOptions(nativeDestinationName.Get(), &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RenameError, false);
return SUCCEEDED(hr);
}
bool QWinRTFileEngine::renameOverwrite(const QString &newName)
{
Q_D(QWinRTFileEngine);
HRESULT hr;
ComPtr<IStorageFolder> destinationFolder;
hr = getDestinationFolder(d->fileName, newName, d->file.Get(), destinationFolder.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RenameError, false);
const QString destinationName = QFileInfo(newName).fileName();
HStringReference nativeDestinationName(reinterpret_cast<LPCWSTR>(destinationName.utf16()),
uint(destinationName.length()));
ComPtr<IAsyncAction> op;
hr = d->file->RenameAsync(nativeDestinationName.Get(), NameCollisionOption_ReplaceExisting, &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::RenameError, false);
return SUCCEEDED(hr);
}
QAbstractFileEngine::FileFlags QWinRTFileEngine::fileFlags(FileFlags type) const
{
Q_D(const QWinRTFileEngine);
FileFlags flags = ExistsFlag|ReadOwnerPerm|ReadUserPerm|WriteOwnerPerm|WriteUserPerm;
HRESULT hr;
FileAttributes attributes;
hr = d->file->get_Attributes(&attributes);
RETURN_IF_FAILED("Failed to get file attributes", return flags);
if (attributes & FileAttributes_ReadOnly)
flags ^= WriteUserPerm;
if (attributes & FileAttributes_Directory)
flags |= DirectoryType;
else
flags |= FileType;
return type & flags;
}
bool QWinRTFileEngine::setPermissions(uint perms)
{
Q_UNUSED(perms);
Q_UNIMPLEMENTED();
return false;
}
QString QWinRTFileEngine::fileName(FileName type) const
{
Q_D(const QWinRTFileEngine);
switch (type) {
default:
case DefaultName:
case AbsoluteName:
case CanonicalName:
break;
case BaseName:
return d->lastSeparator < 0
? d->fileName : d->fileName.mid(d->lastSeparator, d->firstDot - d->lastSeparator);
case PathName:
case AbsolutePathName:
case CanonicalPathName:
return d->fileName.mid(0, d->lastSeparator);
case LinkName:
case BundleName:
return QString();
}
return d->fileName;
}
QDateTime QWinRTFileEngine::fileTime(FileTime type) const
{
Q_D(const QWinRTFileEngine);
HRESULT hr;
DateTime dateTime = { 0 };
switch (type) {
case BirthTime:
hr = d->file->get_DateCreated(&dateTime);
RETURN_IF_FAILED("Failed to get file creation time", return QDateTime());
break;
case MetadataChangeTime:
case ModificationTime:
case AccessTime: {
ComPtr<IAsyncOperation<FileProperties::BasicProperties *>> op;
hr = d->file->GetBasicPropertiesAsync(&op);
RETURN_IF_FAILED("Failed to initiate file properties", return QDateTime());
ComPtr<FileProperties::IBasicProperties> properties;
hr = QWinRTFunctions::await(op, properties.GetAddressOf());
RETURN_IF_FAILED("Failed to get file properties", return QDateTime());
hr = properties->get_DateModified(&dateTime);
RETURN_IF_FAILED("Failed to get file date", return QDateTime());
}
break;
}
SYSTEMTIME systemTime;
FileTimeToSystemTime((const FILETIME *)&dateTime, &systemTime);
QDate date(systemTime.wYear, systemTime.wMonth, systemTime.wDay);
QTime time(systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
return QDateTime(date, time);
}
qint64 QWinRTFileEngine::read(char *data, qint64 maxlen)
{
Q_D(QWinRTFileEngine);
if (!d->stream)
return -1;
ComPtr<IInputStream> stream;
HRESULT hr = d->stream.As(&stream);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
UINT32 length = UINT32(qBound(quint64(0), quint64(maxlen), quint64(UINT32_MAX)));
ComPtr<IBuffer> buffer;
hr = d->bufferFactory->Create(length, &buffer);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> op;
hr = stream->ReadAsync(buffer.Get(), length, InputStreamOptions_None, &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
// Quoting MSDN IInputStream::ReadAsync() documentation:
// "Depending on the implementation, the data that's read might be placed
// into the input buffer, or it might be returned in a different buffer."
// Using GetAddressOf can cause ref counting errors leaking the original
// buffer.
ComPtr<IBuffer> effectiveBuffer;
hr = QWinRTFunctions::await(op, effectiveBuffer.GetAddressOf());
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
hr = effectiveBuffer->get_Length(&length);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
hr = effectiveBuffer.As(&byteArrayAccess);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
byte *bytes;
hr = byteArrayAccess->Buffer(&bytes);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1);
memcpy(data, bytes, length);
return qint64(length);
}
qint64 QWinRTFileEngine::write(const char *data, qint64 maxlen)
{
Q_D(QWinRTFileEngine);
if (!d->stream)
return -1;
ComPtr<IOutputStream> stream;
HRESULT hr = d->stream.As(&stream);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
UINT32 length = UINT32(qBound(quint64(0), quint64(maxlen), quint64(UINT_MAX)));
ComPtr<IBuffer> buffer;
hr = d->bufferFactory->Create(length, &buffer);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
hr = buffer->put_Length(length);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
hr = buffer.As(&byteArrayAccess);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
byte *bytes;
hr = byteArrayAccess->Buffer(&bytes);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
memcpy(bytes, data, length);
ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op;
hr = stream->WriteAsync(buffer.Get(), &op);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
hr = QWinRTFunctions::await(op, &length);
RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1);
return qint64(length);
}
QT_END_NAMESPACE