settingsaccessor.cpp 94.9 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
const char ENVIRONMENT_ID_KEY[] = "ProjectExplorer.Project.Updater.EnvironmentId";
const char VERSION_KEY[] = "ProjectExplorer.Project.Updater.FileVersion";
const char ORIGINAL_VERSION_KEY[] = "OriginalVersion";

57 58 59 60 61 62 63 64 65 66 67 68 69
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

70 71 72
namespace ProjectExplorer {
namespace Internal {

73 74 75 76 77
// --------------------------------------------------------------------
// VersionUpgrader:
// --------------------------------------------------------------------
// Handles updating a QVariantMap from version() - 1 to version()
class VersionUpgrader
78 79
{
public:
80 81
    VersionUpgrader() { }
    virtual ~VersionUpgrader() { }
82

83 84
    virtual int version() const = 0;
    virtual QString backupExtension() const = 0;
85 86

    virtual QVariantMap upgrade(const QVariantMap &data) = 0;
87 88 89

protected:
    typedef QPair<QLatin1String,QLatin1String> Change;
90
    QVariantMap renameKeys(const QList<Change> &changes, QVariantMap map) const;
91 92 93 94 95
};

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

121
using namespace ProjectExplorer;
122
using namespace Internal;
123 124

namespace {
125

126 127
const char USER_STICKY_KEYS_KEY[] = "ProjectExplorer.Project.UserStickyKeys";
const char SHARED_SETTINGS[] = "SharedSettings";
128

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

315 316
} // namespace

Tobias Hunger's avatar
Tobias Hunger committed
317 318 319 320
//
// Helper functions:
//

321 322
QT_BEGIN_NAMESPACE

hjk's avatar
hjk committed
323 324
class HandlerNode
{
325 326 327 328
public:
    QSet<QString> strings;
    QHash<QString, HandlerNode> children;
};
hjk's avatar
hjk committed
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 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
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;
}
374

375 376 377 378 379
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
380
{
381
    // Register Upgraders:
382
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
383 384 385 386 387 388 389 390 391
    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);
392
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
393 394 395
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
396 397
}

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

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

415 416 417 418 419
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        Settings() {}
420
        Settings(const QVariantMap &map) : map(map) {}
421 422 423

        bool isValid() const;

424 425
        QVariantMap map;
        Utils::FileName path;
426 427 428
    };

    Settings bestSettings(const SettingsAccessor *accessor, const QStringList &candidates) const;
429

430 431 432 433 434
    QMap<int, Internal::VersionUpgrader *> m_upgraders;
    Utils::PersistentSettingsWriter *m_writer;
};
} // end namespace

435 436 437
SettingsAccessor::SettingsAccessor(Project *project) :
    m_firstVersion(-1),
    m_lastVersion(-1),
438 439
    m_project(project),
    d(new SettingsAccessorPrivate)
440 441
{
    QTC_CHECK(m_project);
442 443
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
444 445
}

446
SettingsAccessor::~SettingsAccessor()
447
{
448
    delete d;
449 450
}

Tobias Hunger's avatar
Tobias Hunger committed
451 452
Project *SettingsAccessor::project() const
{ return m_project; }
453

454 455
namespace {

456 457 458 459 460 461 462 463 464 465 466 467 468
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();
469
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
470
                continue;
471 472 473 474 475 476 477 478 479 480 481 482 483 484
            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);
485 486 487 488
                continue;
            }
        }
    }
489
};
490

491
class MergeSettingsOperation : public Operation
492
{
493
public:
494
    MergeSettingsOperation(const QSet<QString> &sticky) : m_userSticky(sticky) { }
495

496
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
497 498
    {
        if (!m_userSticky.contains(key))
499
            userMap.insert(key, sharedValue);
500
    }
501 502

private:
503 504 505
    QSet<QString> m_userSticky;
};

506 507 508 509 510 511

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
512
        Q_UNUSED(userMap);
513 514 515 516 517 518 519 520 521 522 523
        m_userSticky.insert(key);
    }

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

private:
    QSet<QString> m_userSticky;
};

} // namespace

524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
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;
}

548 549
/**
 * @brief Upgrade the settings to a target version
550 551 552
 * @param data The settings to upgrade
 * @param toVersion The target version
 * @return Settings of the requested version.
553
 */
554
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data, int toVersion) const
555
{
556 557 558 559
    const int version = versionFromMap(data);

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

561 562 563 564 565
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
566 567 568

    if (version >= toVersion
            || version < m_firstVersion
569
            || toVersion > currentVersion())
570
        return result;
571

572 573
    for (int i = version; i < toVersion; ++i) {
        VersionUpgrader *upgrader = d->m_upgraders.value(i);
574 575
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
576 577
    }

578
    return result;
579 580
}

581 582
namespace {

583 584 585 586 587
// 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.
588
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
589
{
590
    QVariantMap result = userMap;
591
    if (sharedMap.isEmpty())
592
        return result;
593 594
    if (userMap.isEmpty())
        return sharedMap;
595 596

    QSet<QString> stickyKeys;
597
    const QVariant stickyList = result.take(QLatin1String(USER_STICKY_KEYS_KEY)).toList();
598 599 600
    if (stickyList.isValid()) {
        if (stickyList.type() != QVariant::List) {
            // File is messed up... The user probably changed something.
601
            return result;
602
        }
603 604
        foreach (const QVariant &v, stickyList.toList())
            stickyKeys.insert(v.toString());
605
    }
606

607 608 609 610
    // Do not override bookkeeping settings:
    stickyKeys.insert(QLatin1String(ORIGINAL_VERSION_KEY));
    stickyKeys.insert(QLatin1String(VERSION_KEY));

611 612
    MergeSettingsOperation op(stickyKeys);
    op.synchronize(result, sharedMap);
613
    return result;
614 615 616 617 618 619 620 621 622 623
}

// 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".
624
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
625 626 627 628
{
    if (sharedMap.isEmpty())
        return;

629 630
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
631

632
    userMap.insert(QLatin1String(USER_STICKY_KEYS_KEY), QVariant(op.stickySettings()));
633 634 635 636
}

} // Anonymous

637
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
638 639 640
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
641

642
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
643
{
Tobias Hunger's avatar
Tobias Hunger committed
644
    if (m_lastVersion < 0)
645 646
        return QVariantMap();

647 648 649
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
650 651
}

652
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
653
{
Tobias Hunger's avatar
Tobias Hunger committed
654
    if (map.isEmpty())
655 656
        return false;

657 658
    backupUserFile();

659
    SettingsAccessorPrivate::Settings settings(map);
660
    settings.path = FileName::fromString(defaultFileName(m_userSuffix));
Tobias Hunger's avatar
Tobias Hunger committed
661
    const QVariant &shared = m_project->property(SHARED_SETTINGS);
662
    if (shared.isValid())
663
        trackUserStickySettings(settings.map, shared.toMap());
664

665
    if (!d->m_writer || d->m_writer->fileName() != settings.path) {
666
        delete d->m_writer;
667
        d->m_writer = new PersistentSettingsWriter(settings.path, QLatin1String("QtCreatorProject"));
668 669 670 671
    }

    QVariantMap data;

672 673
    for (QVariantMap::const_iterator i = settings.map.constBegin();
         i != settings.map.constEnd();
674 675 676 677 678 679 680
         ++i) {
        data.insert(i.key(), i.value());
    }

    data.insert(QLatin1String(VERSION_KEY), m_lastVersion + 1);
    data.insert(QLatin1String(ENVIRONMENT_ID_KEY), SettingsAccessor::creatorId());
    return d->m_writer->save(data, parent);
681 682
}

683
void SettingsAccessor::addVersionUpgrader(VersionUpgrader *handler)
684
{
685
    const int version(handler->version());
686 687
    QTC_ASSERT(handler, return);
    QTC_ASSERT(version >= 0, return);
688 689
    QTC_ASSERT(!d->m_upgraders.contains(version), return);
    QTC_ASSERT(d->m_upgraders.isEmpty() ||
690 691
               (version == m_lastVersion + 1 || version == m_firstVersion - 1), return);

692
    if (d->m_upgraders.isEmpty()) {
693 694 695 696 697 698 699 700 701
        m_firstVersion = version;
        m_lastVersion = version;
    } else {
        if (version < m_firstVersion)
            m_firstVersion = version;
        if (version > m_lastVersion)
            m_lastVersion = version;
    }

702
    d->m_upgraders.insert(version, handler);
703 704 705 706 707

    // Postconditions:
    Q_ASSERT(m_lastVersion >= 0);
    Q_ASSERT(m_firstVersion >= 0);
    Q_ASSERT(m_lastVersion >= m_firstVersion);
708
    Q_ASSERT(d->m_upgraders.count() == m_lastVersion - m_firstVersion + 1);
709
    for (int i = m_firstVersion; i < m_lastVersion; ++i)
710
        Q_ASSERT(d->m_upgraders.contains(i));
711 712
}

713 714
/* Will always return the default name first */
QStringList SettingsAccessor::findSettingsFiles(const QString &suffix) const
715
{
716 717
    const QString defaultName = defaultFileName(suffix);
    QDir projectDir = QDir(project()->projectDirectory());
718

719 720 721
    QStringList result;
    if (QFileInfo(defaultName).exists())
        result << defaultName;
722

723 724
    QFileInfoList fiList = projectDir.entryInfoList(
                QStringList(QFileInfo(defaultName).fileName() + QLatin1String("*")), QDir::Files);
725

726 727 728 729 730 731 732 733
    foreach (const QFileInfo &fi, fiList) {
        const QString path = fi.absoluteFilePath();
        if (!result.contains(path))
            result.append(path);
    }
    return result;
}

734
QByteArray SettingsAccessor::creatorId()
735
{
736
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
737 738 739 740
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
741
    return project()->projectFilePath() + suffix;
742 743 744 745 746 747 748
}

int SettingsAccessor::currentVersion() const
{
    return m_lastVersion + 1;
}

749 750
void SettingsAccessor::backupUserFile() const
{
751
    SettingsAccessorPrivate::Settings oldSettings;
752 753 754
    oldSettings.path = FileName::fromString(defaultFileName(m_userSuffix));
    oldSettings.map = readFile(oldSettings.path);
    if (oldSettings.map.isEmpty())
755 756 757
        return;

    // Do we need to do a backup?
758
    const QString origName = oldSettings.path.toString();
759
    QString backupName = origName;
760
    const QByteArray oldEnvironmentId = environmentIdFromMap(oldSettings.map);
761 762
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
        backupName += QLatin1String(".") + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
763
    const int oldVersion = versionFromMap(oldSettings.map);
764 765 766
    if (oldVersion != currentVersion()) {
        if (d->m_upgraders.contains(oldVersion))
            backupName += QLatin1String(".") + d->m_upgraders.value(oldVersion)->backupExtension();
767
        else
768
            backupName += QLatin1String(".") + QString::number(oldVersion);
769 770 771 772 773
    }
    if (backupName != origName)
        QFile::copy(origName, backupName);
}

774
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
775
{
776
    SettingsAccessorPrivate::Settings result;
777
    QStringList fileList = findSettingsFiles(m_userSuffix);
778
    if (fileList.isEmpty()) // No settings found at all.
779
        return result.map;
780

781
    result = d->bestSettings(this, fileList);
782

783
    const QByteArray resultEnvironmentId = environmentIdFromMap(result.map);
784

785 786 787
    // Error handling:
    if (!result.isValid()) {
        QMessageBox::information(
788
            parent,
789
            QApplication::translate("ProjectExplorer::SettingsAccessor",
790
                                    "No valid Settings found"),
791
            QApplication::translate("ProjectExplorer::SettingsAccessor",
792
                                    "<p>No valid settings file could be found "
793 794 795 796
                                    "for this installation of Qt Creator.</p>"
                                    "<p>All settings files were either too new or too "
                                    "old to be read.</p>"),
            QMessageBox::Ok);
797
    } else if (!resultEnvironmentId.isEmpty() && resultEnvironmentId != creatorId()) {
798 799 800 801 802 803 804 805 806 807 808 809
        // Wrong environment!
        QMessageBox msgBox(
            QMessageBox::Question,
            QApplication::translate("ProjectExplorer::SettingsAccessor",
                                    "Settings File for '%1' from a different Environment?")
                    .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>"
                                    "<p>Do you still want to load the settings file '%1'?</p>")
810
                    .arg(result.path.toUserOutput()),
811
            QMessageBox::Yes | QMessageBox::No,
812
            parent);
813 814 815
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
816
            result.map.clear();
817
    } else if ((result.path.toString() != defaultFileName(m_userSuffix))
818
               && (versionFromMap(result.map) < currentVersion())) {
819
        QMessageBox::information(
820
                    parent,
821
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
822
                                            "Using Old Settings"),
823
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
824 825 826 827 828 829 830 831
                                            "<p>The versioned backup '%1' of the .user settings "
                                            "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>")
832
                    .arg(result.path.toUserOutput()),
833 834
                    QMessageBox::Ok);
    }
835
    return result.map;
836 837
}

838
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
839
{
840
    SettingsAccessorPrivate::Settings sharedSettings;
841
    QString fn = project()->projectFilePath() + m_sharedSuffix;
842 843
    sharedSettings.path = FileName::fromString(fn);
    sharedSettings.map = readFile(sharedSettings.path);
844

845
    if (versionFromMap(sharedSettings.map) > currentVersion()) {
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
        // 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,
861
                    parent);
862 863 864
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
865
            sharedSettings.map.clear();
866
        else
867
            sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
868
    }
869
    return sharedSettings.map;
870 871
}

872
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor, const QStringList &candidates) const
873
{
874 875 876
    Settings newestNonMatching;
    Settings newestMatching;
    Settings tmp;
877 878

    foreach (const QString &file, candidates) {
879 880 881
        tmp.path = FileName::fromString(file);
        tmp.map = accessor->readFile(tmp.path);
        if (tmp.map.isEmpty())
882 883
            continue;

884
        const int tmpVersion = SettingsAccessor::versionFromMap(tmp.map);
885

886
        if (tmpVersion > accessor->currentVersion()) {
887
            qWarning() << "Skipping settings file" << tmp.path.toUserOutput() << "(too new).";
888 889
            continue;
        }
890
        if (tmpVersion < accessor->m_firstVersion) {
891
            qWarning() << "Skipping settings file" << tmp.path.toUserOutput() << "(too old).";
892
            continue;
893 894
        }

895
        const QByteArray tmpEnvironmentId = SettingsAccessor::environmentIdFromMap(tmp.map);
896
        if (tmpEnvironmentId.isEmpty() || tmpEnvironmentId == SettingsAccessor::creatorId()) {
897
            if (tmpVersion > SettingsAccessor::versionFromMap(newestMatching.map))
898 899
                newestMatching = tmp;
        } else {
900
            if (tmpVersion > SettingsAccessor::versionFromMap(newestNonMatching.map))
901
                newestNonMatching = tmp;
902
        }
903
        if (SettingsAccessor::versionFromMap(newestMatching.map) == accessor->m_lastVersion + 1)
904
            break;
905 906
    }

907
    Settings result;
908 909 910 911 912 913
    if (newestMatching.isValid())
        result = newestMatching;
    else if (newestNonMatching.isValid())
        result = newestNonMatching;

    return result;