/**************************************************************************** ** ** 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 #include #include #include #include #include #include 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 StreamCompletedHandler; typedef IAsyncOperationWithProgressCompletedHandler 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> 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 bufferFactory; QString fileName; int lastSeparator; int firstDot; ComPtr file; ComPtr stream; QIODevice::OpenMode openMode; qint64 pos; private: QWinRTFileEngineHandler *q_ptr; Q_DECLARE_PUBLIC(QWinRTFileEngineHandler) }; QWinRTFileEngineHandler::QWinRTFileEngineHandler() : d_ptr(new QWinRTFileEngineHandlerPrivate) { } QWinRTFileEngineHandler::~QWinRTFileEngineHandler() { } 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>::const_iterator file = d->files.find(fileName); if (file != d->files.end()) return new QWinRTFileEngine(fileName, file.value().Get()); return Q_NULLPTR; } static HRESULT getDestinationFolder(const QString &fileName, const QString &newFileName, IStorageItem *file, IStorageFolder **folder) { HRESULT hr; ComPtr> op; QFileInfo newFileInfo(newFileName); QFileInfo fileInfo(fileName); if (fileInfo.dir() == newFileInfo.dir()) { ComPtr item; hr = file->QueryInterface(IID_PPV_ARGS(&item)); Q_ASSERT_SUCCEEDED(hr); hr = item->GetParentAsync(&op); } else { ComPtr 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(newFilePath.utf16()), 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)) { } QWinRTFileEngine::~QWinRTFileEngine() { } bool QWinRTFileEngine::open(QIODevice::OpenMode openMode) { Q_D(QWinRTFileEngine); FileAccessMode fileAccessMode = (openMode & QIODevice::WriteOnly) ? FileAccessMode_ReadWrite : FileAccessMode_Read; HRESULT hr; ComPtr file; hr = d->file.As(&file); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::OpenError, false); ComPtr> 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); d->openMode = openMode; return SUCCEEDED(hr); } bool QWinRTFileEngine::close() { Q_D(QWinRTFileEngine); if (!d->stream) return false; ComPtr 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 stream; HRESULT hr = d->stream.As(&stream); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, false); ComPtr> 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); } 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(pos); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::PositionError, false); d->pos = pos; return SUCCEEDED(hr); } bool QWinRTFileEngine::remove() { Q_D(QWinRTFileEngine); ComPtr 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 destinationFolder; hr = getDestinationFolder(d->fileName, newName, d->file.Get(), destinationFolder.GetAddressOf()); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false); ComPtr 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(destinationName.utf16()), destinationName.length()); ComPtr> op; hr = file->CopyOverloadDefaultOptions(destinationFolder.Get(), nativeDestinationName.Get(), &op); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::CopyError, false); ComPtr 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 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(destinationName.utf16()), destinationName.length()); ComPtr 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 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(destinationName.utf16()), destinationName.length()); ComPtr 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 CreationTime: hr = d->file->get_DateCreated(&dateTime); RETURN_IF_FAILED("Failed to get file creation time", return QDateTime()); break; case ModificationTime: case AccessTime: { ComPtr> op; hr = d->file->GetBasicPropertiesAsync(&op); RETURN_IF_FAILED("Failed to initiate file properties", return QDateTime()); ComPtr properties; hr = QWinRTFunctions::await(op, properties.GetAddressOf()); RETURN_IF_FAILED("Failed to get file properties", return QDateTime()); hr = type == ModificationTime ? properties->get_DateModified(&dateTime) : properties->get_ItemDate(&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 stream; HRESULT hr = d->stream.As(&stream); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1); UINT32 length = qBound(quint64(0), quint64(maxlen), quint64(UINT_MAX)); ComPtr buffer; hr = d->bufferFactory->Create(length, &buffer); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::ReadError, -1); ComPtr> 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 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 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 stream; HRESULT hr = d->stream.As(&stream); RETURN_AND_SET_ERROR_IF_FAILED(QFileDevice::WriteError, -1); UINT32 length = qBound(quint64(0), quint64(maxlen), quint64(UINT_MAX)); ComPtr 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 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> 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