settingsaccessor.cpp 99.3 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
} // namespace

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

330 331
QT_BEGIN_NAMESPACE

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

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

384 385 386 387 388
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
389
{
390
    // Register Upgraders:
391
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
392 393 394 395 396 397 398 399 400
    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);
401
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
402 403 404
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
405 406 407 408 409 410 411 412 413
    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);
Daniel Teske's avatar
Daniel Teske committed
414 415 416 417
    const QString obsoleteKey = QLatin1String(OBSOLETE_VERSION_KEY);
    if (data.contains(obsoleteKey)) {
        result = setVersionInMap(result, data.value(obsoleteKey, versionFromMap(data)).toInt());
        result.remove(obsoleteKey);
418 419
    }
    return result;
420 421
}

422 423 424 425 426 427 428 429 430
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;
}

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

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

448 449 450 451 452 453
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        bool isValid() const;

454 455
        QVariantMap map;
        Utils::FileName path;
456 457
    };

458 459 460 461 462 463 464 465 466 467
    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;
    }
468
    Settings bestSettings(const SettingsAccessor *accessor, const QList<Utils::FileName> &pathList);
469

470
    QList<VersionUpgrader *> m_upgraders;
471 472 473 474
    Utils::PersistentSettingsWriter *m_writer;
};
} // end namespace

475
SettingsAccessor::SettingsAccessor(Project *project) :
476 477
    m_project(project),
    d(new SettingsAccessorPrivate)
478 479
{
    QTC_CHECK(m_project);
480 481
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
482 483
}

484
SettingsAccessor::~SettingsAccessor()
485
{
486
    delete d;
487 488
}

Tobias Hunger's avatar
Tobias Hunger committed
489 490
Project *SettingsAccessor::project() const
{ return m_project; }
491

492 493
namespace {

494 495 496 497 498 499 500 501 502 503 504 505 506
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();
507
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
508
                continue;
509 510 511 512 513 514 515 516 517 518 519 520 521 522
            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);
523 524 525 526
                continue;
            }
        }
    }
527
};
528

529
class MergeSettingsOperation : public Operation
530
{
531
public:
532
    MergeSettingsOperation(const QSet<QString> &sticky) : m_userSticky(sticky) { }
533

534
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
535 536
    {
        if (!m_userSticky.contains(key))
537
            userMap.insert(key, sharedValue);
538
    }
539 540

private:
541 542 543
    QSet<QString> m_userSticky;
};

544 545 546 547 548 549

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
550
        Q_UNUSED(userMap);
551 552 553 554 555 556 557 558 559 560 561
        m_userSticky.insert(key);
    }

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

private:
    QSet<QString> m_userSticky;
};

} // namespace

562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
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;
}

586 587
/*!
 * Run directly after reading the \a data.
588 589 590 591
 *
 * This method is called right after reading the data before any attempt at interpreting the data
 * is made.
 *
592
 * Returns the prepared data.
593 594 595 596 597 598
 */
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
    return data;
}

599 600
/*!
 * Check which of two sets of data are a better match to load.
601 602 603 604 605
 *
 * 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.
 *
606 607 608
 * Compares \a newData against \a origData.
 *
 * Returns \c true if \a newData is a better match than \a origData and \c false otherwise.
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
 */
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;
}

631 632 633 634
/*!
 * Upgrade the settings in \a data to the version \a toVersion.
 *
 * Returns settings of the requested version.
635
 */
636
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data, int toVersion) const
637
{
638 639 640 641
    const int version = versionFromMap(data);

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

643 644 645 646 647
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
648 649

    if (version >= toVersion
650 651
            || version < d->firstVersion()
            || toVersion > d->currentVersion())
652
        return result;
653

654
    for (int i = version; i < toVersion; ++i) {
655 656
        VersionUpgrader *upgrader = d->upgrader(i);
        QTC_CHECK(upgrader && upgrader->version() == i);
657 658
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
659 660
    }

661
    return result;
662 663
}

664
/*!
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
 * 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));
}

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 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
 * 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);
}

764 765
namespace {

766 767 768 769 770
// 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.
771
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
772
{
773
    QVariantMap result = userMap;
774
    if (sharedMap.isEmpty())
775
        return result;
776 777
    if (userMap.isEmpty())
        return sharedMap;
778 779

    QSet<QString> stickyKeys;
780
    const QVariant stickyList = result.take(QLatin1String(USER_STICKY_KEYS_KEY)).toList();
781 782 783
    if (stickyList.isValid()) {
        if (stickyList.type() != QVariant::List) {
            // File is messed up... The user probably changed something.
784
            return result;
785
        }
786 787
        foreach (const QVariant &v, stickyList.toList())
            stickyKeys.insert(v.toString());
788
    }
789

790 791 792 793
    // Do not override bookkeeping settings:
    stickyKeys.insert(QLatin1String(ORIGINAL_VERSION_KEY));
    stickyKeys.insert(QLatin1String(VERSION_KEY));

794 795
    MergeSettingsOperation op(stickyKeys);
    op.synchronize(result, sharedMap);
796
    return result;
797 798 799 800 801 802 803 804 805 806
}

// 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".
807
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
808 809 810 811
{
    if (sharedMap.isEmpty())
        return;

812 813
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
814

815
    userMap.insert(QLatin1String(USER_STICKY_KEYS_KEY), QVariant(op.stickySettings()));
816 817 818 819
}

} // Anonymous

820
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
821 822 823
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
824

825
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
826
{
827
    if (d->lastVersion() < 0)
828 829
        return QVariantMap();

830 831 832
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
833 834
}

835 836 837 838 839 840 841 842 843 844 845 846 847
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;
}

848
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
849
{
Tobias Hunger's avatar
Tobias Hunger committed
850
    if (map.isEmpty())
851 852
        return false;

853 854
    backupUserFile();

855
    QVariantMap data = prepareToSaveSettings(map);
856 857 858

    Utils::FileName path = FileName::fromString(defaultFileName(m_userSuffix));
    if (!d->m_writer || d->m_writer->fileName() != path) {
859
        delete d->m_writer;
860
        d->m_writer = new PersistentSettingsWriter(path, QLatin1String("QtCreatorProject"));
861 862 863
    }

    return d->m_writer->save(data, parent);
864 865
}

866 867 868 869 870
bool SettingsAccessor::addVersionUpgrader(VersionUpgrader *upgrader)
{
    QTC_ASSERT(upgrader, return false);
    int version = upgrader->version();
    QTC_ASSERT(version >= 0, return false);
871

872 873 874 875 876 877
    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
878

879
    return true;
880 881
}

882
/* Will always return the default name first (if applicable) */
883
QList<FileName> SettingsAccessor::settingsFiles(const QString &suffix) const
884
{
885
    QList<Utils::FileName> result;
886

887 888 889 890 891 892
    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);
893

894
    foreach (const QFileInfo &fi, list) {
895
        const Utils::FileName path = Utils::FileName::fromString(fi.absoluteFilePath());
896 897 898 899 900 901
        if (!result.contains(path)) {
            if (path.endsWith(suffix))
                result.prepend(path);
            else
                result.append(path);
        }
902
    }
903

904 905 906
    return result;
}

907
QByteArray SettingsAccessor::creatorId()
908
{
909
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
910 911 912 913
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
914
    return project()->projectFilePath().toString() + suffix;
915 916 917 918
}

int SettingsAccessor::currentVersion() const
{
919 920 921 922 923 924
    return d->currentVersion();
}

int SettingsAccessor::firstSupportedVersion() const
{
    return d->firstVersion();
925 926
}

927 928
void SettingsAccessor::backupUserFile() const
{
929
    SettingsAccessorPrivate::Settings oldSettings;
930 931 932
    oldSettings.path = FileName::fromString(defaultFileName(m_userSuffix));
    oldSettings.map = readFile(oldSettings.path);
    if (oldSettings.map.isEmpty())
933 934 935
        return;

    // Do we need to do a backup?
936
    const QString origName = oldSettings.path.toString();
937
    QString backupName = origName;
938
    const QByteArray oldEnvironmentId = environmentIdFromMap(oldSettings.map);
939 940
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
        backupName += QLatin1String(".") + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
941
    const int oldVersion = versionFromMap(oldSettings.map);
942
    if (oldVersion != currentVersion()) {
943 944 945
        VersionUpgrader *upgrader = d->upgrader(oldVersion);
        if (upgrader)
            backupName += QLatin1String(".") + upgrader->backupExtension();
946
        else
947
            backupName += QLatin1String(".") + QString::number(oldVersion);
948 949 950 951 952
    }
    if (backupName != origName)
        QFile::copy(origName, backupName);
}

953
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
954
{
955
    SettingsAccessorPrivate::Settings result;
956
    QList<Utils::FileName> fileList = settingsFiles(m_userSuffix);
957
    if (fileList.isEmpty()) // No settings found at all.
958
        return result.map;
959

960
    result = d->bestSettings(this, fileList);
961 962 963 964 965 966
    if (result.path.isEmpty())
        result.path = project()->projectDirectory();

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

968
    return result.map;
969 970
}

971
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
972
{
973
    SettingsAccessorPrivate::Settings sharedSettings;
974
    QString fn = project()->projectFilePath().toString() + m_sharedSuffix;
975 976
    sharedSettings.path = FileName::fromString(fn);
    sharedSettings.map = readFile(sharedSettings.path);
977

978
    if (versionFromMap(sharedSettings.map) > currentVersion()) {
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
        // 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,
994
                    parent);
995 996 997
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
998
            sharedSettings.map.clear();
999
        else
1000
            sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
1001
    }
1002
    return sharedSettings.map;
1003 1004
}

1005 1006
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor,
                                                                        const QList<Utils::FileName> &pathList)
1007
{
1008 1009 1010
    Settings bestMatch;
    foreach (const Utils::FileName &path, pathList) {
        QVariantMap tmp = accessor->readFile(path);
1011

1012 1013
        int version = SettingsAccessor::versionFromMap(tmp);
        if (version < firstVersion() || version > currentVersion())
1014 1015
            continue;

1016 1017 1018
        if (accessor->isBetterMatch(bestMatch.map, tmp)) {
            bestMatch.path = path;
            bestMatch.map = tmp;
1019
        }