settingsaccessor.cpp 97.2 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 49
#include <QApplication>
#include <QMessageBox>
50

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

53 54 55 56 57 58 59 60 61 62 63 64 65
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

66 67 68
namespace ProjectExplorer {
namespace Internal {

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

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

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

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

/**
 * Performs a simple renaming of the listed keys in \a changes recursively on \a map.
 */
92
QVariantMap VersionUpgrader::renameKeys(const QList<Change> &changes, QVariantMap map) const
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
{
    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

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

namespace {
121

122
const char USER_STICKY_KEYS_KEY[] = "UserStickyKeys";
123
const char SHARED_SETTINGS[] = "SharedSettings";
124 125 126 127 128 129
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";
130

Tobias Hunger's avatar
Tobias Hunger committed
131 132 133
// 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).
134
class UserFileVersion1Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
135 136
{
public:
137
    UserFileVersion1Upgrader(UserFileAccessor *a) : m_accessor(a) { }
138 139
    int version() const { return 1; }
    QString backupExtension() const { return QLatin1String("1.3+git"); }
140
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

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;
    };
160

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

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

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

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

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

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

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

218 219 220
// 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.
221
class UserFileVersion8Upgrader : public VersionUpgrader
222 223
{
public:
224 225
    int version() const { return 8; }
    QString backupExtension() const {
226 227 228
        // pre5 because we renamed 2.2 to 2.1 later, so people already have 2.2pre4 files
        return QLatin1String("2.2pre5");
    }
229
    QVariantMap upgrade(const QVariantMap &map);
230 231
};

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

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

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

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

private:
262 263 264 265
    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,
266
                              const QMap<int, QVariantMap> &rcs, int activeRc, const QString &projectDir);
Tobias Hunger's avatar
Tobias Hunger committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283

    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
284
    QHash<Kit *, QVariantMap> m_targets;
285
    UserFileAccessor *m_accessor;
Tobias Hunger's avatar
Tobias Hunger committed
286 287
};

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

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

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

317 318 319 320 321 322 323 324
// 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);
};
325 326
} // namespace

Tobias Hunger's avatar
Tobias Hunger committed
327 328 329 330
//
// Helper functions:
//

331 332
QT_BEGIN_NAMESPACE

hjk's avatar
hjk committed
333 334
class HandlerNode
{
335 336 337 338
public:
    QSet<QString> strings;
    QHash<QString, HandlerNode> children;
};
hjk's avatar
hjk committed
339

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
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;
}
384

385 386 387 388 389
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
390
{
391
    // Register Upgraders:
392
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
393 394 395 396 397 398 399 400 401
    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);
402
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
403 404 405
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
406 407 408 409 410 411 412 413 414 415 416 417 418 419
    addVersionUpgrader(new UserFileVersion15Upgrader);
}

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);
    if (data.contains(QLatin1String(OBSOLETE_VERSION_KEY))) {
        result = setVersionInMap(result, data.value(key, versionFromMap(data)).toInt());
        result.remove(key);
    }
    return result;
420 421
}

422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

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

439 440 441 442 443 444
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        bool isValid() const;

445 446
        QVariantMap map;
        Utils::FileName path;
447 448
    };

449 450 451 452 453 454 455 456 457 458
    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;
    }
459
    Settings bestSettings(const SettingsAccessor *accessor, const QStringList &candidates) const;
460

461
    QList<VersionUpgrader *> m_upgraders;
462 463 464 465
    Utils::PersistentSettingsWriter *m_writer;
};
} // end namespace

466
SettingsAccessor::SettingsAccessor(Project *project) :
467 468
    m_project(project),
    d(new SettingsAccessorPrivate)
469 470
{
    QTC_CHECK(m_project);
471 472
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
473 474
}

475
SettingsAccessor::~SettingsAccessor()
476
{
477
    delete d;
478 479
}

Tobias Hunger's avatar
Tobias Hunger committed
480 481
Project *SettingsAccessor::project() const
{ return m_project; }
482

483 484
namespace {

485 486 487 488 489 490 491 492 493 494 495 496 497
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();
498
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
499
                continue;
500 501 502 503 504 505 506 507 508 509 510 511 512 513
            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);
514 515 516 517
                continue;
            }
        }
    }
518
};
519

520
class MergeSettingsOperation : public Operation
521
{
522
public:
523
    MergeSettingsOperation(const QSet<QString> &sticky) : m_userSticky(sticky) { }
524

525
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
526 527
    {
        if (!m_userSticky.contains(key))
528
            userMap.insert(key, sharedValue);
529
    }
530 531

private:
532 533 534
    QSet<QString> m_userSticky;
};

535 536 537 538 539 540

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
541
        Q_UNUSED(userMap);
542 543 544 545 546 547 548 549 550 551 552
        m_userSticky.insert(key);
    }

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

private:
    QSet<QString> m_userSticky;
};

} // namespace

553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
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;
}

577 578 579 580 581 582 583 584 585 586 587 588 589 590
/**
 * @brief Run directly after reading the data
 *
 * This method is called right after reading the data before any attempt at interpreting the data
 * is made.
 *
 * @param data The input data
 * @return The prepared data.
 */
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
    return data;
}

591 592
/**
 * @brief Upgrade the settings to a target version
593 594 595
 * @param data The settings to upgrade
 * @param toVersion The target version
 * @return Settings of the requested version.
596
 */
597
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data, int toVersion) const
598
{
599 600 601 602
    const int version = versionFromMap(data);

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

604 605 606 607 608
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
609 610

    if (version >= toVersion
611 612
            || version < d->firstVersion()
            || toVersion > d->currentVersion())
613
        return result;
614

615
    for (int i = version; i < toVersion; ++i) {
616 617
        VersionUpgrader *upgrader = d->upgrader(i);
        QTC_CHECK(upgrader && upgrader->version() == i);
618 619
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
620 621
    }

622
    return result;
623 624
}

625 626
namespace {

627 628 629 630 631
// 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.
632
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
633
{
634
    QVariantMap result = userMap;
635
    if (sharedMap.isEmpty())
636
        return result;
637 638
    if (userMap.isEmpty())
        return sharedMap;
639 640

    QSet<QString> stickyKeys;
641
    const QVariant stickyList = result.take(QLatin1String(USER_STICKY_KEYS_KEY)).toList();
642 643 644
    if (stickyList.isValid()) {
        if (stickyList.type() != QVariant::List) {
            // File is messed up... The user probably changed something.
645
            return result;
646
        }
647 648
        foreach (const QVariant &v, stickyList.toList())
            stickyKeys.insert(v.toString());
649
    }
650

651 652 653 654
    // Do not override bookkeeping settings:
    stickyKeys.insert(QLatin1String(ORIGINAL_VERSION_KEY));
    stickyKeys.insert(QLatin1String(VERSION_KEY));

655 656
    MergeSettingsOperation op(stickyKeys);
    op.synchronize(result, sharedMap);
657
    return result;
658 659 660 661 662 663 664 665 666 667
}

// 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".
668
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
669 670 671 672
{
    if (sharedMap.isEmpty())
        return;

673 674
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
675

676
    userMap.insert(QLatin1String(USER_STICKY_KEYS_KEY), QVariant(op.stickySettings()));
677 678 679 680
}

} // Anonymous

681
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
682 683 684
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
685

686
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
687
{
688
    if (d->lastVersion() < 0)
689 690
        return QVariantMap();

691 692 693
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
694 695
}

696
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
697
{
Tobias Hunger's avatar
Tobias Hunger committed
698
    if (map.isEmpty())
699 700
        return false;

701 702
    backupUserFile();

703 704
    SettingsAccessorPrivate::Settings settings;
    settings.map = map;
705
    settings.path = FileName::fromString(defaultFileName(m_userSuffix));
Tobias Hunger's avatar
Tobias Hunger committed
706
    const QVariant &shared = m_project->property(SHARED_SETTINGS);
707
    if (shared.isValid())
708
        trackUserStickySettings(settings.map, shared.toMap());
709

710
    if (!d->m_writer || d->m_writer->fileName() != settings.path) {
711
        delete d->m_writer;
712
        d->m_writer = new PersistentSettingsWriter(settings.path, QLatin1String("QtCreatorProject"));
713 714 715 716
    }

    QVariantMap data;

717 718
    for (QVariantMap::const_iterator i = settings.map.constBegin();
         i != settings.map.constEnd();
719 720 721 722
         ++i) {
        data.insert(i.key(), i.value());
    }

723
    data.insert(QLatin1String(VERSION_KEY), d->currentVersion());
724
    // for compatibility with QtC 3.1 and older:
725
    data.insert(QLatin1String(OBSOLETE_VERSION_KEY), d->currentVersion()); // TODO: Move into UserfileAccessor!
726 727
    data.insert(QLatin1String(ENVIRONMENT_ID_KEY), SettingsAccessor::creatorId());
    return d->m_writer->save(data, parent);
728 729
}

730 731 732 733 734
bool SettingsAccessor::addVersionUpgrader(VersionUpgrader *upgrader)
{
    QTC_ASSERT(upgrader, return false);
    int version = upgrader->version();
    QTC_ASSERT(version >= 0, return false);
735

736 737 738 739 740 741
    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
742

743
    return true;
744 745
}

746 747
/* Will always return the default name first */
QStringList SettingsAccessor::findSettingsFiles(const QString &suffix) const
748
{
749
    const QString defaultName = defaultFileName(suffix);
750
    QDir projectDir = QDir(project()->projectDirectory().toString());
751

752 753 754
    QStringList result;
    if (QFileInfo(defaultName).exists())
        result << defaultName;
755

756 757
    QFileInfoList fiList = projectDir.entryInfoList(
                QStringList(QFileInfo(defaultName).fileName() + QLatin1String("*")), QDir::Files);
758

759 760 761 762 763 764 765 766
    foreach (const QFileInfo &fi, fiList) {
        const QString path = fi.absoluteFilePath();
        if (!result.contains(path))
            result.append(path);
    }
    return result;
}

767
QByteArray SettingsAccessor::creatorId()
768
{
769
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
770 771 772 773
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
774
    return project()->projectFilePath().toString() + suffix;
775 776 777 778
}

int SettingsAccessor::currentVersion() const
{
779 780 781 782 783 784
    return d->currentVersion();
}

int SettingsAccessor::firstSupportedVersion() const
{
    return d->firstVersion();
785 786
}

787 788
void SettingsAccessor::backupUserFile() const
{
789
    SettingsAccessorPrivate::Settings oldSettings;
790 791 792
    oldSettings.path = FileName::fromString(defaultFileName(m_userSuffix));
    oldSettings.map = readFile(oldSettings.path);
    if (oldSettings.map.isEmpty())
793 794 795
        return;

    // Do we need to do a backup?
796
    const QString origName = oldSettings.path.toString();
797
    QString backupName = origName;
798
    const QByteArray oldEnvironmentId = environmentIdFromMap(oldSettings.map);
799 800
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
        backupName += QLatin1String(".") + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
801
    const int oldVersion = versionFromMap(oldSettings.map);
802
    if (oldVersion != currentVersion()) {
803 804 805
        VersionUpgrader *upgrader = d->upgrader(oldVersion);
        if (upgrader)
            backupName += QLatin1String(".") + upgrader->backupExtension();
806
        else
807
            backupName += QLatin1String(".") + QString::number(oldVersion);
808 809 810 811 812
    }
    if (backupName != origName)
        QFile::copy(origName, backupName);
}

813
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
814
{
815
    SettingsAccessorPrivate::Settings result;
816
    QStringList fileList = findSettingsFiles(m_userSuffix);
817
    if (fileList.isEmpty()) // No settings found at all.
818
        return result.map;
819

820
    result = d->bestSettings(this, fileList);
821

822
    const QByteArray resultEnvironmentId = environmentIdFromMap(result.map);
823

824 825 826
    // Error handling:
    if (!result.isValid()) {
        QMessageBox::information(
827
            parent,
828
            QApplication::translate("ProjectExplorer::SettingsAccessor",
829
                                    "No valid Settings found"),
830
            QApplication::translate("ProjectExplorer::SettingsAccessor",
831
                                    "<p>No valid settings file could be found "
832 833 834 835
                                    "for this installation of Qt Creator.</p>"
                                    "<p>All settings files were either too new or too "
                                    "old to be read.</p>"),
            QMessageBox::Ok);
836
    } else if (!resultEnvironmentId.isEmpty() && resultEnvironmentId != creatorId()) {
837 838 839 840
        // Wrong environment!
        QMessageBox msgBox(
            QMessageBox::Question,
            QApplication::translate("ProjectExplorer::SettingsAccessor",
841
                                    "Settings File for \"%1\" from a different Environment?")
842 843 844 845 846 847
                    .arg(project()->displayName()),
            QApplication::translate("ProjectExplorer::SettingsAccessor",
                                    "<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>"
848
                                    "<p>Do you still want to load the settings file \"%1\"?</p>")
849
                    .arg(result.path.toUserOutput()),
850
            QMessageBox::Yes | QMessageBox::No,
851
            parent);
852 853 854
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
855
            result.map.clear();
856
    } else if ((result.path.toString() != defaultFileName(m_userSuffix))
857
               && (versionFromMap(result.map) < currentVersion())) {
858
        QMessageBox::information(
859
                    parent,
860
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
861
                                            "Using Old Settings"),
862
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
863
                                            "<p>The versioned backup \"%1\" of the .user settings "
864 865 866 867 868 869 870
                                            "file is used, because the non-versioned file was "
                                            "created by an incompatible version of Qt Creator.</p>"
                                            "<p>Project settings changes made since "
                                            "the last time this version of Qt Creator was used "
                                            "with this project are ignored, and changes made now "
                                            "will <b>not</b> be propagated to the newer version."
                                            "</p>")
871
                    .arg(result.path.toUserOutput()),
872 873
                    QMessageBox::Ok);
    }
874
    return result.map;
875 876
}

877
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
878
{
879
    SettingsAccessorPrivate::Settings sharedSettings;
880
    QString fn = project()->projectFilePath().toString() + m_sharedSuffix;
881 882
    sharedSettings.path = FileName::fromString(fn);
    sharedSettings.map = readFile(sharedSettings.path);
883

884
    if (versionFromMap(sharedSettings.map) > currentVersion()) {
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
        // 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,
900
                    parent);
901 902 903
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
904
            sharedSettings.map.clear();
905
        else
906
            sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
907
    }
908
    return sharedSettings.map;
909 910
}

911
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor, const QStringList &candidates) const
912
{
913 914 915
    Settings newestNonMatching;
    Settings newestMatching;
    Settings tmp;
916 917

    foreach (const QString &file, candidates) {
918 919 920
        tmp.path = FileName::fromString(file);
        tmp.map = accessor->readFile(tmp.path);
        if (tmp.map.isEmpty())
921 922
            continue;

923
        const int tmpVersion = SettingsAccessor::versionFromMap(tmp.map);
924

925
        if (tmpVersion > accessor->currentVersion()) {
926
            qWarning() << "Skipping settings file" << tmp.path.toUserOutput() << "(too new).";
927 928
            continue;
        }
929
        if (tmpVersion < accessor->firstSupportedVersion()) {