settingsaccessor.cpp 112 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29

30
#include "settingsaccessor.h"
31

32
#include "abi.h"
Tobias Hunger's avatar
Tobias Hunger committed
33
#include "devicesupport/devicemanager.h"
34
#include "project.h"
35
36
#include "projectexplorer.h"
#include "projectexplorersettings.h"
37
38
#include "toolchain.h"
#include "toolchainmanager.h"
Tobias Hunger's avatar
Tobias Hunger committed
39
40
#include "kit.h"
#include "kitmanager.h"
41

dt's avatar
dt committed
42
#include <coreplugin/icore.h>
43
#include <utils/persistentsettings.h>
44
#include <utils/hostosinfo.h>
45
#include <utils/qtcassert.h>
46
#include <utils/qtcprocess.h>
47

48
#include <QApplication>
49

hjk's avatar
hjk committed
50
51
using namespace Utils;

52
53
54
55
56
57
58
59
60
61
62
63
64
namespace {
static QString generateSuffix(const QString &alt1, const QString &alt2)
{
    QString suffix = alt1;
    if (suffix.isEmpty())
        suffix = alt2;
    suffix.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_.-]")), QString(QLatin1Char('_'))); // replace fishy characters:
    if (!suffix.startsWith(QLatin1String(".")))
        suffix.prepend(QLatin1Char('.'));
    return suffix;
}
} // end namespace

65
66
67
namespace ProjectExplorer {
namespace Internal {

68
69
70
71
72
// --------------------------------------------------------------------
// VersionUpgrader:
// --------------------------------------------------------------------
// Handles updating a QVariantMap from version() - 1 to version()
class VersionUpgrader
73
74
{
public:
75
76
    VersionUpgrader() { }
    virtual ~VersionUpgrader() { }
77

78
79
    virtual int version() const = 0;
    virtual QString backupExtension() const = 0;
80
81

    virtual QVariantMap upgrade(const QVariantMap &data) = 0;
82
83
84

protected:
    typedef QPair<QLatin1String,QLatin1String> Change;
85
    QVariantMap renameKeys(const QList<Change> &changes, QVariantMap map) const;
86
87
};

88
/*!
89
90
 * Performs a simple renaming of the listed keys in \a changes recursively on \a map.
 */
91
QVariantMap VersionUpgrader::renameKeys(const QList<Change> &changes, QVariantMap map) const
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
{
    foreach (const Change &change, changes) {
        QVariantMap::iterator oldSetting = map.find(change.first);
        if (oldSetting != map.end()) {
            map.insert(change.second, oldSetting.value());
            map.erase(oldSetting);
        }
    }

    QVariantMap::iterator i = map.begin();
    while (i != map.end()) {
        QVariant v = i.value();
        if (v.type() == QVariant::Map)
            i.value() = renameKeys(changes, v.toMap());

        ++i;
    }

    return map;
}

} // Internal
} // ProjectExplorer

116
using namespace ProjectExplorer;
117
using namespace Internal;
118
119

namespace {
120

121
const char USER_STICKY_KEYS_KEY[] = "UserStickyKeys";
122
const char SHARED_SETTINGS[] = "SharedSettings";
123
124
125
126
127
128
const char ENVIRONMENT_ID_KEY[] = "EnvironmentId";
const char VERSION_KEY[] = "Version";
const char ORIGINAL_VERSION_KEY[] = "OriginalVersion";

// for compatibility with QtC 3.1 and older:
const char OBSOLETE_VERSION_KEY[] = "ProjectExplorer.Project.Updater.FileVersion";
129

Tobias Hunger's avatar
Tobias Hunger committed
130
131
132
// Version 1 is used in master post Qt Creator 1.3.x.
// It was never used in any official release but is required for the
// transition to later versions (which introduce support for targets).
133
class UserFileVersion1Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
134
135
{
public:
136
    UserFileVersion1Upgrader(UserFileAccessor *a) : m_accessor(a) { }
137
138
    int version() const { return 1; }
    QString backupExtension() const { return QLatin1String("1.3+git"); }
139
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

private:
    struct TargetDescription
    {
        TargetDescription(QString tid, QString dn) :
            id(tid),
            displayName(dn)
        {
        }

        TargetDescription(const TargetDescription &td) :
            id(td.id),
            displayName(td.displayName)
        {
        }

        QString id;
        QString displayName;
    };
159

160
    UserFileAccessor *m_accessor;
Tobias Hunger's avatar
Tobias Hunger committed
161
162
};

163
// Version 2 is used in master post Qt Creator 2.0 alpha.
164
class UserFileVersion2Upgrader : public VersionUpgrader
165
166
{
public:
167
168
    int version() const { return 2; }
    QString backupExtension() const { return QLatin1String("2.0-alpha+git"); }
169
    QVariantMap upgrade(const QVariantMap &map);
170
171
};

172
// Version 3 reflect the move of symbian signing from run to build step.
173
class UserFileVersion3Upgrader : public VersionUpgrader
174
175
{
public:
176
177
    int version() const { return 3; }
    QString backupExtension() const { return QLatin1String("2.0-alpha2+git"); }
178
    QVariantMap upgrade(const QVariantMap &map);
179
180
};

Tobias Hunger's avatar
Tobias Hunger committed
181
// Version 4 reflects the introduction of deploy steps
182
class UserFileVersion4Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
183
184
{
public:
185
186
    int version() const { return 4; }
    QString backupExtension() const { return QLatin1String("2.1pre1"); }
187
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
188
189
};

190
// Version 5 reflects the introduction of new deploy steps for Symbian/Maemo
191
class UserFileVersion5Upgrader : public VersionUpgrader
192
193
{
public:
194
195
    int version() const { return 5; }
    QString backupExtension() const { return QLatin1String("2.1pre2"); }
196
    QVariantMap upgrade(const QVariantMap &map);
197
198
};

199
// Version 6 reflects the introduction of new deploy steps for Symbian/Maemo
200
class UserFileVersion6Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
201
202
{
public:
203
204
    int version() const { return 6; }
    QString backupExtension() const { return QLatin1String("2.1pre3"); }
205
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
206
207
};

208
// Version 7 reflects the introduction of new deploy configuration for Symbian
209
class UserFileVersion7Upgrader : public VersionUpgrader
210
211
{
public:
212
213
    int version() const { return 7; }
    QString backupExtension() const { return QLatin1String("2.1pre4"); }
214
    QVariantMap upgrade(const QVariantMap &map);
215
216
};

217
218
219
// Version 8 reflects the change of environment variable expansion rules,
// turning some env variables into expandos, the change of argument quoting rules,
// and the change of VariableManager's expansion syntax.
220
class UserFileVersion8Upgrader : public VersionUpgrader
221
222
{
public:
223
224
    int version() const { return 8; }
    QString backupExtension() const {
225
226
227
        // pre5 because we renamed 2.2 to 2.1 later, so people already have 2.2pre4 files
        return QLatin1String("2.2pre5");
    }
228
    QVariantMap upgrade(const QVariantMap &map);
229
230
};

231
// Version 9 reflects the refactoring of the Maemo deploy step.
232
class UserFileVersion9Upgrader : public VersionUpgrader
233
234
{
public:
235
236
    int version() const { return 9; }
    QString backupExtension() const { return QLatin1String("2.3pre1"); }
237
    QVariantMap upgrade(const QVariantMap &map);
238
239
};

Daniel Teske's avatar
Daniel Teske committed
240
// Version 10 introduces disabling buildsteps, and handles upgrading custom process steps
241
class UserFileVersion10Upgrader : public VersionUpgrader
Daniel Teske's avatar
Daniel Teske committed
242
243
{
public:
244
245
    int version() const { return 10; }
    QString backupExtension() const { return QLatin1String("2.5pre1"); }
246
    QVariantMap upgrade(const QVariantMap &map);
Daniel Teske's avatar
Daniel Teske committed
247
};
248

249
// Version 11 introduces kits
250
class UserFileVersion11Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
251
252
{
public:
253
    UserFileVersion11Upgrader(UserFileAccessor *a) : m_accessor(a) { }
254
    ~UserFileVersion11Upgrader();
Tobias Hunger's avatar
Tobias Hunger committed
255

256
257
    int version() const { return 11; }
    QString backupExtension() const { return QLatin1String("2.6pre1"); }
258
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
259
260

private:
261
262
263
264
    Kit *uniqueKit(Kit *k);
    void addBuildConfiguration(Kit *k, const QVariantMap &bc, int bcPos, int bcCount);
    void addDeployConfiguration(Kit *k, const QVariantMap &dc, int dcPos, int dcActive);
    void addRunConfigurations(Kit *k,
265
                              const QMap<int, QVariantMap> &rcs, int activeRc, const QString &projectDir);
Tobias Hunger's avatar
Tobias Hunger committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

    void parseQtversionFile();
    void parseToolChainFile();

    class ToolChainExtraData {
    public:
        explicit ToolChainExtraData(const QString &mks = QString(), const QString &d = QString()) :
            m_mkspec(mks), m_debugger(d)
        { }

        QString m_mkspec;
        QString m_debugger;
    };

    QHash<QString, ToolChainExtraData> m_toolChainExtras;
    QHash<int, QString> m_qtVersionExtras;

Tobias Hunger's avatar
Tobias Hunger committed
283
    QHash<Kit *, QVariantMap> m_targets;
284
    UserFileAccessor *m_accessor;
Tobias Hunger's avatar
Tobias Hunger committed
285
286
};

287
288
// Version 12 reflects the move of environment settings from CMake/Qt4/Custom into
// LocalApplicationRunConfiguration
289
class UserFileVersion12Upgrader : public VersionUpgrader
290
291
{
public:
292
293
    int version() const { return 12; }
    QString backupExtension() const { return QLatin1String("2.7pre1"); }
294
    QVariantMap upgrade(const QVariantMap &map);
295
296
};

297
298
// Version 13 reflects the move of environment settings from LocalApplicationRunConfiguration
// into the EnvironmentAspect
299
class UserFileVersion13Upgrader : public VersionUpgrader
300
301
{
public:
302
303
    int version() const { return 13; }
    QString backupExtension() const { return QLatin1String("2.8"); }
304
    QVariantMap upgrade(const QVariantMap &map);
305
306
};

307
// Version 14 Move builddir into BuildConfiguration
308
class UserFileVersion14Upgrader : public VersionUpgrader
309
310
{
public:
311
312
    int version() const { return 14; }
    QString backupExtension() const { return QLatin1String("3.0-pre1"); }
313
    QVariantMap upgrade(const QVariantMap &map);
314
315
};

316
317
318
319
320
321
322
323
// Version 15 Use settingsaccessor based class for user file reading/writing
class UserFileVersion15Upgrader : public VersionUpgrader
{
public:
    int version() const { return 15; }
    QString backupExtension() const { return QLatin1String("3.2-pre1"); }
    QVariantMap upgrade(const QVariantMap &map);
};
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354

// Version 16 Changed android deployment
class UserFileVersion16Upgrader : public VersionUpgrader
{
public:
    int version() const { return 16; }
    QString backupExtension() const { return QLatin1String("3.3-pre1"); }
    QVariantMap upgrade(const QVariantMap &data);
private:
    class OldStepMaps
    {
    public:
        QString defaultDisplayName;
        QString displayName;
        QVariantMap androidPackageInstall;
        QVariantMap androidDeployQt;
        bool isEmpty()
        {
            return androidPackageInstall.isEmpty() || androidDeployQt.isEmpty();
        }
    };


    QVariantMap removeAndroidPackageStep(QVariantMap deployMap);
    OldStepMaps extractStepMaps(const QVariantMap &deployMap);
    enum NamePolicy { KeepName, RenameBuildConfiguration };
    QVariantMap insertSteps(QVariantMap buildConfigurationMap,
                            const OldStepMaps &oldStepMap,
                            NamePolicy policy);
};

355
356
} // namespace

Tobias Hunger's avatar
Tobias Hunger committed
357
358
359
360
//
// Helper functions:
//

361
362
QT_BEGIN_NAMESPACE

hjk's avatar
hjk committed
363
364
class HandlerNode
{
365
366
367
368
public:
    QSet<QString> strings;
    QHash<QString, HandlerNode> children;
};
hjk's avatar
hjk committed
369

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
Q_DECLARE_TYPEINFO(HandlerNode, Q_MOVABLE_TYPE);

QT_END_NAMESPACE

static HandlerNode buildHandlerNodes(const char * const **keys)
{
    HandlerNode ret;
    while (const char *rname = *(*keys)++) {
        QString name = QLatin1String(rname);
        if (name.endsWith(QLatin1Char('.'))) {
            HandlerNode sub = buildHandlerNodes(keys);
            foreach (const QString &key, name.split(QLatin1Char('|')))
                ret.children.insert(key, sub);
        } else {
            ret.strings.insert(name);
        }
    }
    return ret;
}

static QVariantMap processHandlerNodes(const HandlerNode &node, const QVariantMap &map,
                                       QVariant (*handler)(const QVariant &var))
{
    QVariantMap result;
    QMapIterator<QString, QVariant> it(map);
    while (it.hasNext()) {
        it.next();
        const QString &key = it.key();
        if (node.strings.contains(key)) {
            result.insert(key, handler(it.value()));
            goto handled;
        }
        if (it.value().type() == QVariant::Map)
            for (QHash<QString, HandlerNode>::ConstIterator subit = node.children.constBegin();
                 subit != node.children.constEnd(); ++subit)
                if (key.startsWith(subit.key())) {
                    result.insert(key, processHandlerNodes(subit.value(), it.value().toMap(), handler));
                    goto handled;
                }
        result.insert(key, it.value());
      handled: ;
    }
    return result;
}
414

415
416
417
418
419
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
420
{
421
    // Register Upgraders:
422
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
423
424
425
426
427
428
429
430
431
    addVersionUpgrader(new UserFileVersion2Upgrader);
    addVersionUpgrader(new UserFileVersion3Upgrader);
    addVersionUpgrader(new UserFileVersion4Upgrader);
    addVersionUpgrader(new UserFileVersion5Upgrader);
    addVersionUpgrader(new UserFileVersion6Upgrader);
    addVersionUpgrader(new UserFileVersion7Upgrader);
    addVersionUpgrader(new UserFileVersion8Upgrader);
    addVersionUpgrader(new UserFileVersion9Upgrader);
    addVersionUpgrader(new UserFileVersion10Upgrader);
432
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
433
434
435
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
436
    addVersionUpgrader(new UserFileVersion15Upgrader);
437
    addVersionUpgrader(new UserFileVersion16Upgrader);
438
439
440
441
442
443
444
445
}

QVariantMap UserFileAccessor::prepareSettings(const QVariantMap &data) const
{
    // Move from old Version field to new one:
    // This can not be done in a normal upgrader since the version information is needed
    // to decide which upgraders to run
    QVariantMap result = SettingsAccessor::prepareSettings(data);
Daniel Teske's avatar
Daniel Teske committed
446
447
448
449
    const QString obsoleteKey = QLatin1String(OBSOLETE_VERSION_KEY);
    if (data.contains(obsoleteKey)) {
        result = setVersionInMap(result, data.value(obsoleteKey, versionFromMap(data)).toInt());
        result.remove(obsoleteKey);
450
451
    }
    return result;
452
453
}

454
455
456
457
458
459
460
461
462
QVariantMap UserFileAccessor::prepareToSaveSettings(const QVariantMap &data) const
{
    QVariantMap tmp = SettingsAccessor::prepareToSaveSettings(data);

    // for compatibility with QtC 3.1 and older:
    tmp.insert(QLatin1String(OBSOLETE_VERSION_KEY), currentVersion());
    return tmp;
}

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

    ~SettingsAccessorPrivate()
    {
        qDeleteAll(m_upgraders);
        delete m_writer;
    }

480
481
482
483
484
485
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        bool isValid() const;

486
487
        QVariantMap map;
        Utils::FileName path;
488
489
    };

490
491
492
493
494
495
496
497
498
499
    int firstVersion() const { return m_upgraders.isEmpty() ? -1 : m_upgraders.first()->version(); }
    int lastVersion() const { return m_upgraders.isEmpty() ? -1 : m_upgraders.last()->version(); }
    int currentVersion() const { return lastVersion() + 1; }
    VersionUpgrader *upgrader(const int version) const
    {
        int pos = version - firstVersion();
        if (pos >= 0 && pos < m_upgraders.count())
            return m_upgraders.at(pos);
        return 0;
    }
500
    Settings bestSettings(const SettingsAccessor *accessor, const QList<Utils::FileName> &pathList);
501

502
    QList<VersionUpgrader *> m_upgraders;
503
504
505
506
    Utils::PersistentSettingsWriter *m_writer;
};
} // end namespace

507
SettingsAccessor::SettingsAccessor(Project *project) :
508
509
    m_project(project),
    d(new SettingsAccessorPrivate)
510
511
{
    QTC_CHECK(m_project);
512
513
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
514
515
}

516
SettingsAccessor::~SettingsAccessor()
517
{
518
    delete d;
519
520
}

Tobias Hunger's avatar
Tobias Hunger committed
521
522
Project *SettingsAccessor::project() const
{ return m_project; }
523

524
525
namespace {

526
527
528
529
530
531
532
533
534
535
536
537
538
class Operation {
public:
    virtual ~Operation() { }

    virtual void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue) = 0;

    void synchronize(QVariantMap &userMap, const QVariantMap &sharedMap)
    {
        QVariantMap::const_iterator it = sharedMap.begin();
        QVariantMap::const_iterator eit = sharedMap.end();

        for (; it != eit; ++it) {
            const QString &key = it.key();
539
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
540
                continue;
541
542
543
544
545
546
547
548
549
550
551
552
553
554
            const QVariant &sharedValue = it.value();
            const QVariant &userValue = userMap.value(key);
            if (sharedValue.type() == QVariant::Map) {
                if (userValue.type() != QVariant::Map) {
                    // This should happen only if the user manually changed the file in such a way.
                    continue;
                }
                QVariantMap nestedUserMap = userValue.toMap();
                synchronize(nestedUserMap, sharedValue.toMap());
                userMap.insert(key, nestedUserMap);
                continue;
            }
            if (userMap.contains(key) && userValue != sharedValue) {
                apply(userMap, key, sharedValue);
555
556
557
558
                continue;
            }
        }
    }
559
};
560

561
class MergeSettingsOperation : public Operation
562
{
563
public:
564
    MergeSettingsOperation(const QSet<QString> &sticky) : m_userSticky(sticky) { }
565

566
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
567
568
    {
        if (!m_userSticky.contains(key))
569
            userMap.insert(key, sharedValue);
570
    }
571
572

private:
573
574
575
    QSet<QString> m_userSticky;
};

576
577
578
579
580
581

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
582
        Q_UNUSED(userMap);
583
584
585
586
587
588
589
590
591
592
593
        m_userSticky.insert(key);
    }

    QStringList stickySettings() const { return m_userSticky.toList(); }

private:
    QSet<QString> m_userSticky;
};

} // namespace

594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
int SettingsAccessor::versionFromMap(const QVariantMap &data)
{
    return data.value(QLatin1String(VERSION_KEY), -1).toInt();
}

int SettingsAccessor::originalVersionFromMap(const QVariantMap &data)
{
    return data.value(QLatin1String(ORIGINAL_VERSION_KEY), versionFromMap(data)).toInt();
}

QVariantMap SettingsAccessor::setOriginalVersionInMap(const QVariantMap &data, int version)
{
    QVariantMap result = data;
    result.insert(QLatin1String(ORIGINAL_VERSION_KEY), version);
    return result;
}

QVariantMap SettingsAccessor::setVersionInMap(const QVariantMap &data, int version)
{
    QVariantMap result = data;
    result.insert(QLatin1String(VERSION_KEY), version);
    return result;
}

618
619
/*!
 * Run directly after reading the \a data.
620
621
622
623
 *
 * This method is called right after reading the data before any attempt at interpreting the data
 * is made.
 *
624
 * Returns the prepared data.
625
626
627
628
629
630
 */
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
    return data;
}

631
632
/*!
 * Check which of two sets of data are a better match to load.
633
634
635
636
637
 *
 * This method is used to compare data extracted from two XML settings files.
 * It will never be called with a version too old or too new to be read by
 * the current instance of Qt Creator.
 *
638
639
640
 * Compares \a newData against \a origData.
 *
 * Returns \c true if \a newData is a better match than \a origData and \c false otherwise.
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
 */
bool SettingsAccessor::isBetterMatch(const QVariantMap &origData, const QVariantMap &newData) const
{
    if (origData.isEmpty())
        return true;

    int origVersion = versionFromMap(origData);
    QByteArray origEnv = environmentIdFromMap(origData);

    int newVersion = versionFromMap(newData);
    QByteArray newEnv = environmentIdFromMap(newData);

    if (origEnv != newEnv) {
        if (origEnv == creatorId())
            return false;
        if (newEnv == creatorId())
            return true;
    }

    return newVersion > origVersion;
}

663
664
665
666
/*!
 * Upgrade the settings in \a data to the version \a toVersion.
 *
 * Returns settings of the requested version.
667
 */
668
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data, int toVersion) const
669
{
670
671
672
673
    const int version = versionFromMap(data);

    if (data.isEmpty())
        return data;
674

675
676
677
678
679
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
680

681
682
683
684
685
    if (version < 15 && toVersion < 15) {
        result.insert(QLatin1String(USER_STICKY_KEYS_KEY),
                      data.value(QLatin1String("ProjectExplorer.Project.UserStickyKeys")));
    }

686
    if (version >= toVersion
687
688
            || version < d->firstVersion()
            || toVersion > d->currentVersion())
689
        return result;
690

691
    for (int i = version; i < toVersion; ++i) {
692
693
        VersionUpgrader *upgrader = d->upgrader(i);
        QTC_CHECK(upgrader && upgrader->version() == i);
694
695
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
696
697
    }

698
    return result;
699
700
}

701
/*!
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
 * Find issues with the settings file and warn the user about them.
 *
 * \a data is the data from the settings file.
 * \a path is the full path to the settings used.
 * \a parent is the widget to be set as parent of any dialogs that are opened.
 *
 * Returns \c true if the settings are not usable anymore and \c false otherwise.
 */
SettingsAccessor::ProceedInfo SettingsAccessor::reportIssues(const QVariantMap &data,
                                                             const Utils::FileName &path,
                                                             QWidget *parent) const
{
    IssueInfo issue = findIssues(data, path);
    QMessageBox::Icon icon = QMessageBox::Information;

    if (issue.buttons.count() > 1)
        icon = QMessageBox::Question;

    QMessageBox::StandardButtons buttons = QMessageBox::NoButton;
    foreach (QMessageBox::StandardButton b, issue.buttons.keys())
        buttons |= b;

    if (buttons == QMessageBox::NoButton)
        return Continue;

    QMessageBox msgBox(icon, issue.title, issue.message, buttons, parent);
    if (issue.defaultButton != QMessageBox::NoButton)
        msgBox.setDefaultButton(issue.defaultButton);
    if (issue.escapeButton != QMessageBox::NoButton)
        msgBox.setEscapeButton(issue.escapeButton);

    int boxAction = msgBox.exec();
    return issue.buttons.value(static_cast<QMessageBox::StandardButton>(boxAction));
}

737
/*!
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
 * Checks \a data located at \a path for issues to be displayed with reportIssues.
 *
 * Returns a IssueInfo object which is then used by reportIssues.
 */
SettingsAccessor::IssueInfo SettingsAccessor::findIssues(const QVariantMap &data, const Utils::FileName &path) const
{
    SettingsAccessor::IssueInfo result;

    Utils::FileName defaultSettingsPath = project()->projectFilePath();
    defaultSettingsPath.appendString(m_userSuffix);

    int version = versionFromMap(data);
    if (!path.toFileInfo().exists()) {
        return result;
    } else if (data.isEmpty() || version < firstSupportedVersion() || version > currentVersion()) {
        result.title = QApplication::translate("Utils::SettingsAccessor", "No Valid Settings Found");
        result.message = QApplication::translate("Utils::SettingsAccessor",
                                                 "<p>No valid settings file could be found.</p>"
                                                 "<p>All settings files found in directory \"%1\" "
                                                 "were either too new or too old to be read.</p>")
                .arg(path.toUserOutput());
        result.buttons.insert(QMessageBox::Ok, DiscardAndContinue);
    } else if ((path != defaultSettingsPath) && (version < currentVersion())) {
        result.title = QApplication::translate("Utils::SettingsAccessor", "Using Old Settings");
        result.message = QApplication::translate("Utils::SettingsAccessor",
                                                 "<p>The versioned backup \"%1\" of the settings "
                                                 "file is used, because the non-versioned file was "
                                                 "created by an incompatible version of Qt Creator.</p>"
                                                 "<p>Settings changes made since the last time this "
                                                 "version of Qt Creator was used are ignored, and "
                                                 "changes made now will <b>not</b> be propagated to "
                                                 "the newer version.</p>").arg(path.toUserOutput());
        result.buttons.insert(QMessageBox::Ok, Continue);
    }

    if (!result.buttons.isEmpty())
        return result;

    QByteArray readId = environmentIdFromMap(data);
    if (!readId.isEmpty() && readId != creatorId()) {
        result.title = differentEnvironmentMsg(project()->displayName());
        result.message = QApplication::translate("ProjectExplorer::EnvironmentIdAccessor",
                                                 "<p>No .user settings file created by this instance "
                                                 "of Qt Creator was found.</p>"
                                                 "<p>Did you work with this project on another machine or "
                                                 "using a different settings path before?</p>"
                                                 "<p>Do you still want to load the settings file \"%1\"?</p>")
                .arg(path.toUserOutput());
        result.defaultButton = QMessageBox::No;
        result.escapeButton = QMessageBox::No;
        result.buttons.insert(QMessageBox::Yes, SettingsAccessor::Continue);
        result.buttons.insert(QMessageBox::No, SettingsAccessor::DiscardAndContinue);
    }
    return result;
}

QString SettingsAccessor::differentEnvironmentMsg(const QString &projectName)
{
    return QApplication::translate("ProjectExplorer::EnvironmentIdAccessor",
                                   "Settings File for \"%1\" from a different Environment?")
            .arg(projectName);
}

801
802
namespace {

803
804
805
806
807
// When restoring settings...
//   We check whether a .shared file exists. If so, we compare the settings in this file with
//   corresponding ones in the .user file. Whenever we identify a corresponding setting which
//   has a different value and which is not marked as sticky, we merge the .shared value into
//   the .user value.
808
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
809
{
810
    QVariantMap result = userMap;
811
    if (sharedMap.isEmpty())
812
        return result;
813
814
    if (userMap.isEmpty())
        return sharedMap;
815
816

    QSet<QString> stickyKeys;
817
    const QVariant stickyList = result.take(QLatin1String(USER_STICKY_KEYS_KEY)).toList();
818
819
820
    if (stickyList.isValid()) {
        if (stickyList.type() != QVariant::List) {
            // File is messed up... The user probably changed something.
821
            return result;
822
        }
823
824
        foreach (const QVariant &v, stickyList.toList())
            stickyKeys.insert(v.toString());
825
    }
826

827
828
829
830
    // Do not override bookkeeping settings:
    stickyKeys.insert(QLatin1String(ORIGINAL_VERSION_KEY));
    stickyKeys.insert(QLatin1String(VERSION_KEY));

831
832
    MergeSettingsOperation op(stickyKeys);
    op.synchronize(result, sharedMap);
833
    return result;
834
835
836
837
838
839
840
841
842
843
}

// When saving settings...
//   If a .shared file was considered in the previous restoring step, we check whether for
//   any of the current .shared settings there's a .user one which is different. If so, this
//   means the user explicitly changed it and we mark this setting as sticky.
//   Note that settings are considered sticky only when they differ from the .shared ones.
//   Although this approach is more flexible than permanent/forever sticky settings, it has
//   the side-effect that if a particular value unintentionally becomes the same in both
//   the .user and .shared files, this setting will "unstick".
844
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
845
846
847
848
{
    if (sharedMap.isEmpty())
        return;

849
850
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
851

852
    userMap.insert(QLatin1String(USER_STICKY_KEYS_KEY), QVariant(op.stickySettings()));
853
854
855
856
}

} // Anonymous

857
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
858
859
860
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
861

862
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
863
{
864
    if (d->lastVersion() < 0)
865
866
        return QVariantMap();

867
868
869
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
870
871
}

872
873
874
875
876
877
878
879
880
881
882
883
884
QVariantMap SettingsAccessor::prepareToSaveSettings(const QVariantMap &data) const
{
    QVariantMap tmp = data;
    const QVariant &shared = m_project->property(SHARED_SETTINGS);
    if (shared.isValid())
        trackUserStickySettings(tmp, shared.toMap());

    tmp.insert(QLatin1String(VERSION_KEY), d->currentVersion());
    tmp.insert(QLatin1String(ENVIRONMENT_ID_KEY), SettingsAccessor::creatorId());

    return tmp;
}

885
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
886
{
Tobias Hunger's avatar
Tobias Hunger committed
887
    if (map.isEmpty())
888
889
        return false;

890
891
    backupUserFile();

892
    QVariantMap data = prepareToSaveSettings(map);
893
894
895

    Utils::FileName path = FileName::fromString(defaultFileName(m_userSuffix));
    if (!d->m_writer || d->m_writer->fileName() != path) {
896
        delete d->m_writer;
897
        d->m_writer = new PersistentSettingsWriter(path, QLatin1String("QtCreatorProject"));
898
899
900
    }

    return d->m_writer->save(data, parent);
901
902
}

903
904
905
906
907
bool SettingsAccessor::addVersionUpgrader(VersionUpgrader *upgrader)
{
    QTC_ASSERT(upgrader, return false);
    int version = upgrader->version();
    QTC_ASSERT(version >= 0, return false);
908

909
910
911
912
913
914
    if (d->m_upgraders.isEmpty() || d->currentVersion() == version)
        d->m_upgraders.append(upgrader);
    else if (d->firstVersion() - 1 == version)
        d->m_upgraders.prepend(upgrader);
    else
        QTC_ASSERT(false, return false); // Upgrader was added out of sequence or twice
915

916
    return true;
917
918
}

919
/* Will always return the default name first (if applicable) */
920
QList<FileName> SettingsAccessor::settingsFiles(const QString &suffix) const
921
{
922
    QList<Utils::FileName> result;
923

924
925
926
927
928
929
    const Utils::FileName baseName = project()->projectFilePath();
    QFileInfo fi = baseName.toFileInfo();
    QDir dir = QDir(fi.absolutePath());
    QString filter = fi.fileName() + suffix + QLatin1String("*");

    QFileInfoList list = dir.entryInfoList(QStringList() << filter, QDir::Files);
930

931
    foreach (const QFileInfo &fi, list) {
932
        const Utils::FileName path = Utils::FileName::fromString(fi.absoluteFilePath());
933
934
935
936
937
938
        if (!result.contains(path)) {
            if (path.endsWith(suffix))
                result.prepend(path);
            else
                result.append(path);
        }
939
    }
940

941
942
943
    return result;
}

944
QByteArray SettingsAccessor::creatorId()
945
{
946
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
947
948
949
950
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
951
    return project()->projectFilePath().toString() + suffix;
952
953
954
955
}

int SettingsAccessor::currentVersion() const
{
956
957
958
959
960
961
    return d->currentVersion();
}

int SettingsAccessor::firstSupportedVersion() const
{
    return d->firstVersion();
962
963
}

964
Utils::FileName SettingsAccessor::backupName(const QVariantMap &data) const
965
{
966
967
    QString backupName = defaultFileName(m_userSuffix);
    const QByteArray oldEnvironmentId = environmentIdFromMap(data);
968
969
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
        backupName += QLatin1String(".") + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
970
    const int oldVersion = versionFromMap(data);
971
    if (oldVersion != currentVersion()) {
972
973
974
        VersionUpgrader *upgrader = d->upgrader(oldVersion);
        if (upgrader)
            backupName += QLatin1String(".") + upgrader->backupExtension();
975
        else
976
            backupName += QLatin1String(".") + QString::number(oldVersion);
977
    }
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
    return Utils::FileName::fromString(backupName);
}

void SettingsAccessor::backupUserFile() const
{
    SettingsAccessorPrivate::Settings oldSettings;
    oldSettings.path = FileName::fromString(defaultFileName(m_userSuffix));
    oldSettings.map = readFile(oldSettings.path);
    if (oldSettings.map.isEmpty())
        return;

    // Do we need to do a backup?
    const QString origName = oldSettings.path.toString();
    QString backupFileName = backupName(oldSettings.map).toString();
    if (backupFileName != origName)
        QFile::copy(origName, backupFileName);
994
995
}

996
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
997
{
998
    SettingsAccessorPrivate::Settings result;
999
    QList<Utils::FileName> fileList = settingsFiles(m_userSuffix);
1000
    if (fileList.isEmpty()) // No settings found at all.
1001
        return result.map;
1002

1003
    result = d->bestSettings(this, fileList);
1004
1005
1006
1007
1008
1009
    if (result.path.isEmpty())
        result.path = project()->projectDirectory();

    ProceedInfo proceed = reportIssues(result.map, result.path, parent);
    if (proceed == DiscardAndContinue)
        return QVariantMap();
1010

1011
    return result.map;
1012
1013
}

1014
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
1015
{
1016
    SettingsAccessorPrivate::Settings sharedSettings;
1017
    QString fn = project()->projectFilePath().toString() + m_sharedSuffix;
1018
1019
    sharedSettings.path = FileName::fromString(fn);
    sharedSettings.map = readFile(sharedSettings.path);
1020

1021
    if (versionFromMap(sharedSettings.map) > currentVersion()) {
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
        // The shared file version is newer than Creator... If we have valid user
        // settings we prompt the user whether we could try an *unsupported* update.
        // This makes sense since the merging operation will only replace shared settings
        // that perfectly match corresponding user ones. If we don't have valid user
        // settings to compare against, there's nothing we can do.

        QMessageBox msgBox(
                    QMessageBox::Question,
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
                                            "Unsupported Shared Settings File"),
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
                                            "The version of your .shared file is not "
                                            "supported by Qt Creator. "
                                            "Do you want to try loading it anyway?"),
                    QMessageBox::Yes | QMessageBox::No,
1037
                    parent);
1038
1039
1040
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
1041
            sharedSettings.map.clear();
1042
        else
1043
            sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
1044
    }
1045
    return sharedSettings.map;
1046
1047
}

1048
1049
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor,
                                                                        const QList<Utils::FileName> &pathList)
1050
{
1051
1052
1053
    Settings bestMatch;
    foreach (const Utils::FileName &path, pathList) {
        QVariantMap tmp = accessor->readFile(path);
1054

1055
1056
        int version = SettingsAccessor::versionFromMap(tmp);
        if (version < firstVersion() || version > currentVersion())
1057
1058
            continue;

1059
1060
1061
        if (accessor->isBetterMatch(bestMatch.map, tmp)) {
            bestMatch.path = path;
            bestMatch.map = tmp;
1062
        }
1063
    }
1064
    return bestMatch;
1065
1066
}

1067
1068
QVariantMap SettingsAccessor::mergeSettings(const QVariantMap &userMap,
                                            const QVariantMap &sharedMap) const
1069
{
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
    QVariantMap newUser = userMap;
    QVariantMap newShared = sharedMap;
    QVariantMap result;
    if (!newUser.isEmpty() && !newShared.isEmpty()) {
        newUser = upgradeSettings(newUser, versionFromMap(newShared));
        newShared = upgradeSettings(newShared, versionFromMap(newUser));
        result = mergeSharedSettings(newUser, newShared);
    } else if (!sharedMap.isEmpty()) {
        result = sharedMap;
    } else if (!userMap.isEmpty()) {
        result = userMap;
1081
1082
    }

1083
    m_project->setProperty(SHARED_SETTINGS, newShared);
1084
1085

    // Update from the base version to Creator's version.
1086
    return upgradeSettings(result, currentVersion());
1087
1088
}

1089
1090
1091
// -------------------------------------------------------------------------
// SettingsData
// -------------------------------------------------------------------------
1092
bool SettingsAccessorPrivate::Settings::isValid() const
1093
{
1094
    return SettingsAccessor::versionFromMap(map) > -1 && !path.isEmpty();