2
* Copyright 2013 Canonical Ltd.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License as published by
6
* the Free Software Foundation; version 3.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Author: Zsombor Egri <zsombor.egri@canonical.com>
19
#include "statesaverbackend_p.h"
20
#include "ucapplication.h"
21
#include <QtQml/QQmlContext>
22
#include <QtQml/QQmlProperty>
23
#include <QtQml/qqmlinfo.h>
24
#include <QtQml/qqml.h>
25
#include <QtCore/QCoreApplication>
26
#include <QtCore/QFile>
27
#include <QtCore/QStringList>
29
#include "quickutils.h"
30
#include <QtCore/QStandardPaths>
32
#include "unixsignalhandler_p.h"
34
StateSaverBackend::StateSaverBackend(QObject *parent)
37
, m_globalEnabled(true)
39
// connect to application quit signal so when that is called, we can clean the states saved
40
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
41
this, &StateSaverBackend::cleanup);
42
QObject::connect(&QuickUtils::instance(), &QuickUtils::activated,
43
this, &StateSaverBackend::reset);
44
QObject::connect(&QuickUtils::instance(), &QuickUtils::deactivated,
45
this, &StateSaverBackend::initiateStateSaving);
46
// catch eventual app name changes so we can have different path for the states if needed
47
QObject::connect(&UCApplication::instance(), &UCApplication::applicationNameChanged,
48
this, &StateSaverBackend::initialize);
49
if (!UCApplication::instance().applicationName().isEmpty()) {
53
UnixSignalHandler::instance().connectSignal(UnixSignalHandler::Terminate);
54
UnixSignalHandler::instance().connectSignal(UnixSignalHandler::Interrupt);
55
QObject::connect(&UnixSignalHandler::instance(), SIGNAL(signalTriggered(int)),
56
this, SLOT(signalHandler(int)));
59
StateSaverBackend::~StateSaverBackend()
66
void StateSaverBackend::initialize()
69
// delete previous archive
70
QFile archiveFile(m_archive.data()->fileName());
72
delete m_archive.data();
75
QString applicationName(UCApplication::instance().applicationName());
76
if (applicationName.isEmpty()) {
77
qCritical() << "[StateSaver] Cannot create appstate file, application name not defined.";
80
// make sure the path is in sync with https://wiki.ubuntu.com/SecurityTeam/Specifications/ApplicationConfinement
81
// the file must be saved under XDG_RUNTIME_DIR/<APP_PKGNAME> path.
82
// NOTE!!: we cannot use QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)
83
// as that is going to perform a chmod +w on the path, see bug #1359831. Therefore we must
84
// fetch the XDG_RUNTIME_DIR either from QStandardPaths::standardLocations() or from env var
85
// see bug https://bugreports.qt-project.org/browse/QTBUG-41735
86
QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
87
if (runtimeDir.isEmpty()) {
88
runtimeDir = qgetenv("XDG_RUNTIME_DIR");
90
if (runtimeDir.isEmpty()) {
91
qCritical() << "[StateSaver] No XDG_RUNTIME_DIR path set, cannot create appstate file.";
94
m_archive = new QSettings(QString("%1/%2/statesaver.appstate").
96
arg(applicationName), QSettings::NativeFormat);
97
m_archive->setFallbacksEnabled(false);
100
void StateSaverBackend::cleanup()
106
void StateSaverBackend::signalHandler(int type)
108
if (type == UnixSignalHandler::Interrupt) {
109
Q_EMIT initiateStateSaving();
110
// disconnect aboutToQuit() so the state file doesn't get wiped upon quit
111
QObject::disconnect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
112
this, &StateSaverBackend::cleanup);
114
QCoreApplication::quit();
117
bool StateSaverBackend::enabled() const
119
return m_globalEnabled;
121
void StateSaverBackend::setEnabled(bool enabled)
123
if (m_globalEnabled != enabled) {
124
m_globalEnabled = enabled;
125
Q_EMIT enabledChanged(m_globalEnabled);
126
if (!m_globalEnabled) {
132
bool StateSaverBackend::registerId(const QString &id)
134
if (m_register.contains(id)) {
137
m_register.insert(id);
141
void StateSaverBackend::removeId(const QString &id)
143
m_register.remove(id);
146
int StateSaverBackend::load(const QString &id, QObject *item, const QStringList &properties)
148
if (m_archive.isNull()) {
153
// save the previous group
154
bool restorePreviousGroup = !m_archive->group().isEmpty();
155
if (restorePreviousGroup) {
156
m_groupStack.push(m_archive->group());
157
// leave the group so we can read the next one
158
m_archive->endGroup();
160
m_archive.data()->beginGroup(id);
161
QStringList propertyNames = m_archive.data()->childKeys();
162
Q_FOREACH(const QString &propertyName, propertyNames) {
163
QVariant value = m_archive.data()->value(propertyName);
164
if (!properties.contains(propertyName)) {
168
QQmlProperty qmlProperty(item, propertyName.toLocal8Bit().constData(), qmlContext(item));
169
if (qmlProperty.isValid() && qmlProperty.isWritable()) {
170
QVariant type = m_archive.data()->value(propertyName + "_TYPE");
171
value.convert(type.toInt());
172
bool writeSuccess = qmlProperty.write(value);
176
qmlInfo(item) << UbuntuI18n::instance().tr("property \"%1\" of "
177
"object %2 has type %3 and cannot be set to value \"%4\" of"
178
" type %5").arg(propertyName)
179
.arg(qmlContext(item)->nameForObject(item))
180
.arg(qmlProperty.propertyTypeName())
181
.arg(value.toString())
182
.arg(value.typeName());
185
qmlInfo(item) << UbuntuI18n::instance().tr("property \"%1\" does not exist or is not writable for object %2")
186
.arg(propertyName).arg(qmlContext(item)->nameForObject(item));
189
// drop cache once properties are successfully restored
190
m_archive.data()->remove("");
191
m_archive.data()->endGroup();
192
// restore leaved group if needed
193
if (restorePreviousGroup) {
194
m_archive->beginGroup(m_groupStack.pop());
199
int StateSaverBackend::save(const QString &id, QObject *item, const QStringList &properties)
201
if (m_archive.isNull()) {
204
m_archive.data()->beginGroup(id);
206
Q_FOREACH(const QString &propertyName, properties) {
207
QQmlProperty qmlProperty(item, propertyName.toLocal8Bit().constData());
208
if (qmlProperty.isValid()) {
209
QVariant value = qmlProperty.read();
210
if (static_cast<QMetaType::Type>(value.type()) != QMetaType::QObjectStar) {
211
if (value.userType() == qMetaTypeId<QJSValue>()) {
212
value = value.value<QJSValue>().toVariant();
214
m_archive.data()->setValue(propertyName, value);
215
/* Save the type of the property along with its value.
216
* This is important because QSettings deserializes values as QString.
217
* Setting these strings to QML properties usually works because the
218
* implicit type conversion from string to the type of the QML property
219
* usually works. In some cases cases however (e.g. enum) it fails.
221
* See Qt Bug: https://bugreports.qt-project.org/browse/QTBUG-40474
223
m_archive.data()->setValue(propertyName + "_TYPE", QVariant::fromValue((int)value.type()));
228
m_archive.data()->endGroup();
229
m_archive.data()->sync();
234
* The method resets the register and the state archive for the application.
236
bool StateSaverBackend::reset()
240
QFile archiveFile(m_archive.data()->fileName());
241
return archiveFile.remove();