blob: e91d4d64a2a8727436c377b62bda94bd2c96d654 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module 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 "qsettings.h"
#include "qsettings_p.h"
#include "qvector.h"
#include "qmap.h"
#include "qdebug.h"
#include "qfunctions_winrt.h"
#include <wrl.h>
#include <wrl/event.h>
#include <Windows.ApplicationModel.h>
#include <windows.storage.h>
using namespace ABI::Windows::ApplicationModel;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
typedef ITypedEventHandler<ApplicationData*, IInspectable*> DataHandler;
typedef Collections::IKeyValuePair<HSTRING, ApplicationDataContainer*> ContainerItem;
typedef Collections::IIterable<ContainerItem*> ContainerIterable;
typedef Collections::IIterator<ContainerItem*> ContainerIterator;
typedef Collections::IKeyValuePair<HSTRING, IInspectable*> ValueItem;
typedef Collections::IIterable<ValueItem*> ValueIterable;
typedef Collections::IIterator<ValueItem*> ValueIterator;
QT_BEGIN_NAMESPACE
static IApplicationDataContainer *subContainer(IApplicationDataContainer *parent, const QString &name)
{
ComPtr<IMapView<HSTRING, ApplicationDataContainer*>> childrenContainer;
HRESULT hr = parent->get_Containers(&childrenContainer);
if (FAILED(hr))
return 0;
ComPtr< ContainerIterable > iterable;
ComPtr< ContainerIterator > iterator;
hr = childrenContainer.As(&iterable);
if (FAILED(hr))
return 0;
hr = iterable->First(&iterator);
if (FAILED(hr))
return 0;
boolean current;
hr = iterator->get_HasCurrent(&current);
if (FAILED(hr))
return 0;
while (SUCCEEDED(hr) && current) {
ComPtr<ContainerItem> item;
hr = iterator->get_Current(&item);
if (FAILED(hr))
return 0;
HString key;
hr = item->get_Key(key.GetAddressOf());
if (FAILED(hr))
continue;
QString subName = QString::fromWCharArray(key.GetRawBuffer(nullptr));
if (name == subName) {
IApplicationDataContainer *container;
hr = item->get_Value(&container);
return SUCCEEDED(hr) ? container : 0;
}
hr = iterator->MoveNext(&current);
}
return 0;
}
static QStringList subContainerNames(IApplicationDataContainer *container, bool recursive = false)
{
QStringList result;
ComPtr<IMapView<HSTRING, ApplicationDataContainer*>> childrenContainer;
HRESULT hr = container->get_Containers(&childrenContainer);
if (FAILED(hr))
return result;
ComPtr< ContainerIterable > iterable;
ComPtr< ContainerIterator > iterator;
hr = childrenContainer.As(&iterable);
if (FAILED(hr))
return result;
hr = iterable->First(&iterator);
if (FAILED(hr))
return result;
boolean current;
hr = iterator->get_HasCurrent(&current);
if (FAILED(hr))
return result;
while (SUCCEEDED(hr) && current) {
ComPtr<ContainerItem> item;
hr = iterator->get_Current(&item);
if (FAILED(hr))
return result;
HString key;
hr = item->get_Key(key.GetAddressOf());
if (SUCCEEDED(hr)) {
QString subName = QString::fromWCharArray(key.GetRawBuffer(nullptr));
result.append(subName);
if (recursive) {
ComPtr<IApplicationDataContainer> sub = subContainer(container, subName);
QStringList subSubNames = subContainerNames(sub.Get(), recursive);
for (int i = 0; i < subSubNames.size(); ++i)
subSubNames[i] = subName + QLatin1Char('/') + subSubNames[i];
result.append(subSubNames);
}
hr = iterator->MoveNext(&current);
}
}
return result;
}
static QStringList keyNames(IApplicationDataContainer *container) {
HRESULT hr;
QStringList result;
ComPtr<IPropertySet> values;
hr = container->get_Values(&values);
if (FAILED(hr))
return result;
ComPtr<IMap<HSTRING, IInspectable*>> settingsMap;
hr = values.As(&settingsMap);
if (FAILED(hr))
return result;
ComPtr<IMapView<HSTRING, IInspectable*>> mapView;
hr = settingsMap->GetView(&mapView);
if (FAILED(hr))
return result;
ComPtr< ValueIterable > iterable;
ComPtr< ValueIterator > iterator;
hr = mapView.As(&iterable);
if (FAILED(hr))
return result;
boolean current = false;
hr = iterable->First(&iterator);
if (FAILED(hr))
return result;
hr = iterator->get_HasCurrent(&current);
if (FAILED(hr))
return result;
while (SUCCEEDED(hr) && current){
ComPtr<ValueItem> item;
hr = iterator->get_Current(&item);
if (FAILED(hr))
return result;
HString key;
hr = item->get_Key(key.GetAddressOf());
if (SUCCEEDED(hr)) {
result += QString::fromWCharArray(key.GetRawBuffer(nullptr));
hr = iterator->MoveNext(&current);
}
}
return result;
}
static IApplicationDataContainer *createSubContainer(IApplicationDataContainer *parent, const QString &name)
{
HStringReference childGroupNativeName((const wchar_t*)name.utf16(), name.size());
IApplicationDataContainer *result = subContainer(parent, name);
if (!result)
parent->CreateContainer(childGroupNativeName.Get(), ApplicationDataCreateDisposition_Always, &result);
return result;
}
#define PROP_CASE_TO_VARIANT(TYPE, VARTYPE, QTYPE) \
case PropertyType_##TYPE: { \
VARTYPE v; \
value->Get##TYPE(&v); \
result.setValue( QTYPE(v) ); \
break; \
}
static QVariant propertyValueToQVariant(IPropertyValue *value)
{
QVariant result;
PropertyType type;
value->get_Type(&type);
switch (type) {
PROP_CASE_TO_VARIANT(Boolean, boolean, bool)
PROP_CASE_TO_VARIANT(UInt8, UINT8, quint8)
PROP_CASE_TO_VARIANT(Int16, INT16, qint16)
PROP_CASE_TO_VARIANT(UInt16, UINT16, quint16)
PROP_CASE_TO_VARIANT(Int32, INT32, qint32)
PROP_CASE_TO_VARIANT(UInt32, UINT32, quint32)
PROP_CASE_TO_VARIANT(Int64, INT64, qint64)
PROP_CASE_TO_VARIANT(UInt64, UINT64, quint64)
PROP_CASE_TO_VARIANT(Single, FLOAT, float)
PROP_CASE_TO_VARIANT(Double, DOUBLE, double)
case PropertyType_StringArray: {
UINT32 size;
HSTRING *content;
value->GetStringArray(&size, &content);
QStringList list;
// The last item is assumed to be added by us
for (UINT32 i = 0; i < size - 1; ++i) {
QString s = QString::fromWCharArray(WindowsGetStringRawBuffer(content[i], nullptr));
list.append(s);
}
result = QSettingsPrivate::stringListToVariantList(list);
break;
}
case PropertyType_String: {
HString v;
value->GetString(v.GetAddressOf());
result = QSettingsPrivate::stringToVariant(QString::fromWCharArray(v.GetRawBuffer(nullptr)));
break;
}
default: {
UINT32 size;
BYTE *arr;
value->GetUInt8Array(&size, &arr);
QByteArray data = QByteArray::fromRawData((const char*)arr, size);
QString s;
if (size) {
// We assume this is our qt stored data like on other platforms
// as well. QList and others are converted to byte arrays
s = QString::fromWCharArray((const wchar_t *)data.constData(), data.size() / 2);
result = QSettingsPrivate::stringToVariant(s);
}
break;
}
}
return result;
}
class QWinRTSettingsPrivate : public QSettingsPrivate
{
public:
QWinRTSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application);
QWinRTSettingsPrivate(const QString &rKey);
~QWinRTSettingsPrivate();
void remove(const QString &uKey) override;
void set(const QString &uKey, const QVariant &value) override;
bool get(const QString &uKey, QVariant *value) const override;
QStringList children(const QString &uKey, ChildSpec spec) const override;
void clear() override;
void sync() override;
void flush() override;
bool isWritable() const override;
QString fileName() const override;
private:
void init(QSettings::Scope scope);
IApplicationDataContainer *getContainer(IApplicationDataContainer *parent, const QString &group, bool create = false) const;
void clearContainerMaps();
HRESULT onDataChanged(IApplicationData*, IInspectable*);
ComPtr<IApplicationData> applicationData;
QVector<ComPtr<IApplicationDataContainer>> readContainers;
ComPtr<IApplicationDataContainer> writeContainer;
EventRegistrationToken dataChangedToken;
};
QWinRTSettingsPrivate::QWinRTSettingsPrivate(QSettings::Scope scope, const QString &organization,
const QString &application)
: QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
, writeContainer(0)
{
init(scope);
}
QWinRTSettingsPrivate::QWinRTSettingsPrivate(const QString &rPath)
: QSettingsPrivate(QSettings::NativeFormat, QSettings::UserScope, rPath, QString())
, writeContainer(0)
{
init(QSettings::UserScope);
}
QWinRTSettingsPrivate::~QWinRTSettingsPrivate()
{
clearContainerMaps();
}
void QWinRTSettingsPrivate::remove(const QString &uKey)
{
int lastIndex = uKey.lastIndexOf(QLatin1Char('/'));
QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString();
QString groupKey = uKey.mid(lastIndex + 1);
ComPtr<IApplicationDataContainer> container = getContainer(writeContainer.Get(), groupName, false);
if (!container)
return;
HRESULT hr;
ComPtr<IPropertySet> values;
hr = container->get_Values(&values);
if (FAILED(hr))
return;
ComPtr<IMap<HSTRING, IInspectable*>> settingsMap;
hr = values.As(&settingsMap);
if (FAILED(hr))
return;
HStringReference ref((const wchar_t*)groupKey.utf16(), groupKey.size());
hr = settingsMap->Remove(ref.Get());
// groupKey can be a container as well
hr = container->DeleteContainer(ref.Get());
init(scope);
}
void QWinRTSettingsPrivate::set(const QString &uKey, const QVariant &value)
{
int lastIndex = uKey.lastIndexOf(QLatin1Char('/'));
QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString();
QString groupKey = uKey.mid(lastIndex + 1);
ComPtr<IApplicationDataContainer> container = getContainer(writeContainer.Get(), groupName, true);
ComPtr<IPropertySet> values;
HRESULT hr = container->get_Values(&values);
if (FAILED(hr)) {
qErrnoWarning(hr, "Could not access Windows container values");
setStatus(QSettings::AccessError);
return;
}
ComPtr<IMap<HSTRING, IInspectable*>> settingsMap;
hr = values.As(&settingsMap);
if (FAILED(hr)) {
setStatus(QSettings::AccessError);
return;
}
ComPtr<IPropertyValueStatics> valueStatics;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_PropertyValue).Get(), &valueStatics);
if (FAILED(hr)) {
setStatus(QSettings::AccessError);
return;
}
ComPtr<IInspectable> val;
switch (value.type()) {
case QVariant::List:
case QVariant::StringList: {
QStringList l = variantListToStringList(value.toList());
QStringList::const_iterator it = l.constBegin();
bool containsNull = false;
for (; it != l.constEnd(); ++it) {
if ((*it).length() == 0 || it->contains(QChar::Null)) {
// We can only store as binary
containsNull = true;
break;
}
}
if (containsNull) {
// Store binary
const QString s = variantToString(value);
hr = valueStatics->CreateUInt8Array(s.length() * 2, (BYTE*) s.utf16(), &val);
} else {
// Store as native string list
int size = l.size();
HSTRING *nativeHandleList = new HSTRING[size+1];
for (int i = 0; i < size; ++i)
hr = WindowsCreateString((const wchar_t*)l[i].utf16(), l[i].size(), &nativeHandleList[i]);
// Add end marker
hr = WindowsCreateString((const wchar_t*)L"\0\0@", 3, &nativeHandleList[size]);
hr = valueStatics->CreateStringArray(size + 1 , nativeHandleList, &val);
for (int i = 0; i < size; ++i)
hr = WindowsDeleteString(nativeHandleList[i]);
delete [] nativeHandleList;
}
break;
}
case QVariant::Bool:
hr = valueStatics->CreateBoolean(boolean(value.toBool()), &val);
break;
case QVariant::Int:
hr = valueStatics->CreateInt32(INT32(value.toInt()), &val);
break;
case QVariant::UInt:
hr = valueStatics->CreateUInt32(UINT32(value.toUInt()), &val);
break;
case QVariant::LongLong:
hr = valueStatics->CreateInt64(INT64(value.toLongLong()), &val);
break;
case QVariant::ULongLong:
hr = valueStatics->CreateUInt64(UINT64(value.toULongLong()), &val);
break;
default: {
const QString s = variantToString(value);
if (s.contains(QChar::Null)) {
hr = valueStatics->CreateUInt8Array(s.length() * 2, (BYTE*) s.utf16(), &val);
} else {
HStringReference ref((const wchar_t*)s.utf16(), s.size());
hr = valueStatics->CreateString(ref.Get(), &val);
}
break;
}
}
RETURN_VOID_IF_FAILED("QSettings: Could not save QVariant value into IInspectable");
HStringReference key((const wchar_t*)groupKey.utf16(), groupKey.size());
boolean rep;
hr = settingsMap->Insert(key.Get(), val.Get(), &rep);
RETURN_VOID_IF_FAILED("QSettings: Could not store value");
}
bool QWinRTSettingsPrivate::get(const QString &uKey, QVariant *value) const
{
int lastIndex = uKey.lastIndexOf(QLatin1Char('/'));
QString groupName = (lastIndex > 0) ? uKey.left(lastIndex) : QString();
QString groupKey = uKey.mid(lastIndex + 1);
HRESULT hr;
for (int i = 0; i < readContainers.size(); ++i) {
ComPtr<IApplicationDataContainer> container = const_cast<QWinRTSettingsPrivate*>(this)->getContainer(readContainers.at(i).Get(), groupName);
if (!container)
continue;
ComPtr<IPropertySet> values;
hr = container->get_Values(&values);
if (FAILED(hr))
continue;
ComPtr<IMap<HSTRING, IInspectable*>> settingsMap;
hr = values.As(&settingsMap);
if (FAILED(hr))
continue;
HStringReference key((const wchar_t*)groupKey.utf16(), groupKey.size());
boolean exists;
hr = settingsMap.Get()->HasKey(key.Get(), &exists);
if (FAILED(hr))
continue;
if (!exists) {
if (!fallbacks)
break;
else
continue;
}
if (value) {
ComPtr<IInspectable> val;
hr = settingsMap->Lookup(key.Get(), &val);
if (FAILED(hr))
return false;
ComPtr<IPropertyValue> pVal;
hr = val.As(&pVal);
if (FAILED(hr))
return false;
*value = propertyValueToQVariant(pVal.Get());
}
return true;
}
setStatus(QSettings::AccessError);
return false;
}
QStringList QWinRTSettingsPrivate::children(const QString &uKey, ChildSpec spec) const
{
QStringList result;
for (int i = 0; i < readContainers.size(); ++i) {
ComPtr<IApplicationDataContainer> container = getContainer(readContainers.at(i).Get(), uKey, false);
if (!container.Get())
continue;
// Get Keys in this container
if (spec == AllKeys || spec == ChildKeys)
result += keyNames(container.Get());
// Get Subcontainer(s)
if (spec == AllKeys || spec == ChildGroups) {
const QStringList subContainerList = subContainerNames(container.Get(), spec == AllKeys);
if (spec == AllKeys) {
for (const QString &item : subContainerList) {
const QString subChildren = uKey.isEmpty() ? item : (uKey + QLatin1Char('/') + item);
const QStringList subResult = children(subChildren, ChildKeys);
for (const QString &subItem : subResult)
result += item + QLatin1Char('/') + subItem;
}
}
if (spec == ChildGroups)
result += subContainerList;
}
}
result.removeDuplicates();
return result;
}
void QWinRTSettingsPrivate::clear()
{
ComPtr<IApplicationDataContainer> container;
HRESULT hr;
if (scope == QSettings::UserScope)
hr = applicationData->get_LocalSettings(&container);
else
hr = applicationData->get_RoamingSettings(&container);
RETURN_VOID_IF_FAILED("Could not access settings container");
QString containerName = applicationName.isEmpty() ? organizationName : applicationName;
HStringReference containerNativeName((const wchar_t*)containerName.utf16(), containerName.size());
hr = container->DeleteContainer(containerNativeName.Get());
RETURN_VOID_IF_FAILED("Could not delete Container");
init(scope);
}
void QWinRTSettingsPrivate::sync()
{
// No native sync available
}
void QWinRTSettingsPrivate::flush()
{
// No native flush available
}
QString QWinRTSettingsPrivate::fileName() const
{
Q_UNIMPLEMENTED();
return QString();
}
HRESULT QWinRTSettingsPrivate::onDataChanged(IApplicationData *, IInspectable *)
{
// This only happens, if roaming data is changed by the OS.
// To ensure sanity we clean up the map and start from scratch
init(scope);
return S_OK;
}
void QWinRTSettingsPrivate::init(QSettings::Scope scope)
{
clearContainerMaps();
ComPtr<IApplicationDataStatics> applicationDataStatics;
HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_ApplicationData).Get(), &applicationDataStatics);
if (FAILED(hr)) {
qErrnoWarning(hr, "Could not access Storage Factory");
setStatus(QSettings::AccessError);
return;
}
hr = applicationDataStatics->get_Current(&applicationData);
if (FAILED(hr)) {
qErrnoWarning(hr, "Could not access application data statics");
setStatus(QSettings::AccessError);
return;
}
const QString organizationString = organizationName.isEmpty() ? QLatin1String("OrganizationDefaults") : organizationName;
ComPtr<IApplicationDataContainer> localContainer;
if (scope == QSettings::UserScope && SUCCEEDED(applicationData->get_LocalSettings(&localContainer))) {
if (!applicationName.isEmpty())
readContainers.append(createSubContainer(localContainer.Get(), applicationName));
readContainers.append(createSubContainer(localContainer.Get(), organizationString));
}
ComPtr<IApplicationDataContainer> roamingContainer;
if (SUCCEEDED(applicationData->get_RoamingSettings(&roamingContainer))) {
if (!applicationName.isEmpty())
readContainers.append(createSubContainer(roamingContainer.Get(), applicationName));
readContainers.append(createSubContainer(roamingContainer.Get(), organizationString));
}
ComPtr<IApplicationDataContainer> writeRootContainer = (scope == QSettings::UserScope) ? localContainer : roamingContainer;
if (!applicationName.isEmpty())
writeContainer = createSubContainer(writeRootContainer.Get(), applicationName);
else
writeContainer = createSubContainer(writeRootContainer.Get(), organizationString);
hr = applicationData->add_DataChanged(Callback<DataHandler>(this, &QWinRTSettingsPrivate::onDataChanged).Get(), &dataChangedToken);
}
IApplicationDataContainer *QWinRTSettingsPrivate::getContainer(IApplicationDataContainer *parent, const QString &group, bool create) const
{
IApplicationDataContainer *current = parent;
if (group.isEmpty())
return current;
const QStringList groupPath = group.split(QLatin1Char('/'), QString::SkipEmptyParts);
for (const QString &subGroup : groupPath) {
ComPtr<IApplicationDataContainer> sub = subContainer(current, subGroup);
if (!sub && create)
sub = createSubContainer(current, subGroup);
if (!sub)
return 0; // Something seriously went wrong
current = sub.Detach();
}
return current;
}
void QWinRTSettingsPrivate::clearContainerMaps()
{
readContainers.clear();
writeContainer.Reset();
}
bool QWinRTSettingsPrivate::isWritable() const
{
return true;
}
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
if (format == QSettings::NativeFormat)
return new QWinRTSettingsPrivate(scope, organization, application);
else
return new QConfFileSettingsPrivate(format, scope, organization, application);
}
QSettingsPrivate *QSettingsPrivate::create(const QString &fileName, QSettings::Format format)
{
if (format == QSettings::NativeFormat)
return new QWinRTSettingsPrivate(fileName);
else
return new QConfFileSettingsPrivate(fileName, format);
}
QT_END_NAMESPACE