settingsaccessor.cpp 115 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
** 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
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** 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
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30

31
#include "settingsaccessor.h"
32

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

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

49
#include <QApplication>
50
#include <QDir>
51

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

54 55 56 57 58 59 60
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:
61
    if (!suffix.startsWith(QLatin1Char('.')))
62 63 64 65 66
        suffix.prepend(QLatin1Char('.'));
    return suffix;
}
} // end namespace

67 68 69
namespace ProjectExplorer {
namespace Internal {

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

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

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

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

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

118
using namespace ProjectExplorer;
hjk's avatar
hjk committed
119
using namespace ProjectExplorer::Internal;
120 121

namespace {
122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

318 319 320 321 322 323 324 325
// 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);
};
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

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


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

357 358 359 360 361 362 363 364 365 366 367 368 369 370
// Version 17 Apply user sticky keys per map
class UserFileVersion17Upgrader : public VersionUpgrader
{
public:
    int version() const { return 17; }
    QString backupExtension() const { return QLatin1String("3.3-pre2"); }
    QVariantMap upgrade(const QVariantMap &map);

    QVariant process(const QVariant &entry);

private:
    QVariantList m_sticky;
};

371 372
} // namespace

Tobias Hunger's avatar
Tobias Hunger committed
373 374 375 376
//
// Helper functions:
//

377 378
QT_BEGIN_NAMESPACE

hjk's avatar
hjk committed
379 380
class HandlerNode
{
381 382 383 384
public:
    QSet<QString> strings;
    QHash<QString, HandlerNode> children;
};
hjk's avatar
hjk committed
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
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;
}
430

431 432 433 434 435
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
436
{
437
    // Register Upgraders:
438
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
439 440 441 442 443 444 445 446 447
    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);
448
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
449 450 451
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
452
    addVersionUpgrader(new UserFileVersion15Upgrader);
453
    addVersionUpgrader(new UserFileVersion16Upgrader);
454
    addVersionUpgrader(new UserFileVersion17Upgrader);
455 456 457 458 459 460 461 462
}

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
463 464 465 466
    const QString obsoleteKey = QLatin1String(OBSOLETE_VERSION_KEY);
    if (data.contains(obsoleteKey)) {
        result = setVersionInMap(result, data.value(obsoleteKey, versionFromMap(data)).toInt());
        result.remove(obsoleteKey);
467 468
    }
    return result;
469 470
}

471 472 473 474 475 476 477 478 479
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;
}

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

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

497 498 499 500 501 502
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        bool isValid() const;

503 504
        QVariantMap map;
        Utils::FileName path;
505 506
    };

507 508 509 510 511 512 513 514 515 516
    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;
    }
517
    Settings bestSettings(const SettingsAccessor *accessor, const QList<Utils::FileName> &pathList);
518

519
    QList<VersionUpgrader *> m_upgraders;
520 521
    Utils::PersistentSettingsWriter *m_writer;
};
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595

// Return path to shared directory for .user files, create if necessary.
static inline QString determineSharedUserFileDir()
{
    const char userFilePathVariable[] = "QTC_USER_FILE_PATH";
    if (!qEnvironmentVariableIsSet(userFilePathVariable))
        return QString();
    const QFileInfo fi(QFile::decodeName(qgetenv(userFilePathVariable)));
    const QString path = fi.absoluteFilePath();
    if (fi.isDir() || fi.isSymLink())
        return path;
    if (fi.exists()) {
        qWarning() << userFilePathVariable << '=' << QDir::toNativeSeparators(path)
            << " points to an existing file";
        return QString();
    }
    QDir dir;
    if (!dir.mkpath(path)) {
        qWarning() << "Cannot create: " << QDir::toNativeSeparators(path);
        return QString();
    }
    return path;
}

static QString sharedUserFileDir()
{
    static const QString sharedDir = determineSharedUserFileDir();
    return sharedDir;
}

// Return a suitable relative path to be created under the shared .user directory.
static QString makeRelative(QString path)
{
    const QChar slash(QLatin1Char('/'));
    // Windows network shares: "//server.domain-a.com/foo' -> 'serverdomainacom/foo'
    if (path.startsWith(QLatin1String("//"))) {
        path.remove(0, 2);
        const int nextSlash = path.indexOf(slash);
        if (nextSlash > 0) {
            for (int p = nextSlash; p >= 0; --p) {
                if (!path.at(p).isLetterOrNumber())
                    path.remove(p, 1);
            }
        }
        return path;
    }
    // Windows drives: "C:/foo' -> 'c/foo'
    if (path.size() > 3 && path.at(1) == QLatin1Char(':')) {
        path.remove(1, 1);
        path[0] = path.at(0).toLower();
        return path;
    }
    if (path.startsWith(slash)) // Standard UNIX paths: '/foo' -> 'foo'
        path.remove(0, 1);
    return path;
}

// Return complete file path of the .user file.
static Utils::FileName userFilePath(const ProjectExplorer::Project *project, const QString &suffix)
{
    Utils::FileName result;
    const Utils::FileName projectFilePath = project->projectFilePath();
    if (sharedUserFileDir().isEmpty()) {
        result = projectFilePath;
    } else {
        // Recreate the relative project file hierarchy under the shared directory.
        // PersistentSettingsWriter::write() takes care of creating the path.
        result = Utils::FileName::fromString(sharedUserFileDir());
        result.appendString(QLatin1Char('/') + makeRelative(projectFilePath.toString()));
    }
    result.appendString(suffix);
    return result;
}

596 597
} // end namespace

598
SettingsAccessor::SettingsAccessor(Project *project) :
599 600
    m_project(project),
    d(new SettingsAccessorPrivate)
601 602
{
    QTC_CHECK(m_project);
603 604
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
605 606
}

607
SettingsAccessor::~SettingsAccessor()
608
{
609
    delete d;
610 611
}

Tobias Hunger's avatar
Tobias Hunger committed
612 613
Project *SettingsAccessor::project() const
{ return m_project; }
614

615 616
namespace {

617 618 619 620 621 622 623 624 625 626 627 628 629
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();
630
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
631
                continue;
632 633 634 635 636 637 638 639 640 641 642 643 644 645
            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);
646 647 648 649
                continue;
            }
        }
    }
650
};
651

652
class MergeSettingsOperation : public Operation
653
{
654 655
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
656
    {
657 658 659 660
        // Do not override bookkeeping settings:
        if (key == QLatin1String(ORIGINAL_VERSION_KEY) || key == QLatin1String(VERSION_KEY))
            return;
        if (!userMap.value(QLatin1String(USER_STICKY_KEYS_KEY)).toList().contains(key))
661
            userMap.insert(key, sharedValue);
662 663 664
    }
};

665 666 667 668 669 670

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
671 672 673 674
        const QString stickyKey = QLatin1String(USER_STICKY_KEYS_KEY);
        QVariantList sticky = userMap.value(stickyKey).toList();
        sticky.append(key);
        userMap.insert(stickyKey, sticky);
675 676 677 678 679
    }
};

} // namespace

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
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;
}

704 705
/*!
 * Run directly after reading the \a data.
706 707 708 709
 *
 * This method is called right after reading the data before any attempt at interpreting the data
 * is made.
 *
710
 * Returns the prepared data.
711 712 713 714 715 716
 */
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
    return data;
}

717 718
/*!
 * Check which of two sets of data are a better match to load.
719 720 721 722 723
 *
 * 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.
 *
724 725 726
 * Compares \a newData against \a origData.
 *
 * Returns \c true if \a newData is a better match than \a origData and \c false otherwise.
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
 */
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;
}

749 750 751 752
/*!
 * Upgrade the settings in \a data to the version \a toVersion.
 *
 * Returns settings of the requested version.
753
 */
754
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data) const
755
{
756 757 758 759
    const int version = versionFromMap(data);

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

761 762 763 764 765
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
766

767 768
    const int toVersion = currentVersion();
    if (version >= toVersion || version < d->firstVersion())
769
        return result;
770

771
    for (int i = version; i < toVersion; ++i) {
772 773
        VersionUpgrader *upgrader = d->upgrader(i);
        QTC_CHECK(upgrader && upgrader->version() == i);
774 775
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
776 777
    }

778
    return result;
779 780
}

781
/*!
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
 * 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));
}

817
/*!
818 819 820 821 822 823 824 825
 * 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;

826
    const Utils::FileName defaultSettingsPath = userFilePath(project(), m_userSuffix);
827 828

    int version = versionFromMap(data);
hjk's avatar
hjk committed
829
    if (!path.exists()) {
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
        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);
}

880 881
namespace {

882 883 884 885 886
// 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.
887
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
888
{
889
    QVariantMap result = userMap;
890
    if (sharedMap.isEmpty())
891
        return result;
892 893
    if (userMap.isEmpty())
        return sharedMap;
894

895
    MergeSettingsOperation op;
896
    op.synchronize(result, sharedMap);
897
    return result;
898 899 900 901 902 903 904 905 906 907
}

// 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".
908
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
909 910 911 912
{
    if (sharedMap.isEmpty())
        return;

913 914
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
915 916 917 918
}

} // Anonymous

919
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
920 921 922
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
923

924
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
925
{
926
    if (d->lastVersion() < 0)
927 928
        return QVariantMap();

929 930 931
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
932 933
}

934 935 936 937 938 939 940 941 942 943 944 945 946
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;
}

947
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
948
{
Tobias Hunger's avatar
Tobias Hunger committed
949
    if (map.isEmpty())
950 951
        return false;

952 953
    backupUserFile();

954
    QVariantMap data = prepareToSaveSettings(map);
955 956 957

    Utils::FileName path = FileName::fromString(defaultFileName(m_userSuffix));
    if (!d->m_writer || d->m_writer->fileName() != path) {
958
        delete d->m_writer;
959
        d->m_writer = new PersistentSettingsWriter(path, QLatin1String("QtCreatorProject"));
960 961 962
    }

    return d->m_writer->save(data, parent);
963 964
}

965 966 967 968 969
bool SettingsAccessor::addVersionUpgrader(VersionUpgrader *upgrader)
{
    QTC_ASSERT(upgrader, return false);
    int version = upgrader->version();
    QTC_ASSERT(version >= 0, return false);
970

971 972 973 974 975 976
    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
977

978
    return true;
979 980
}

981
/* Will always return the default name first (if applicable) */
982
QList<FileName> SettingsAccessor::settingsFiles(const QString &suffix) const
983
{
984
    QList<Utils::FileName> result;
985

986 987 988
    QFileInfoList list;
    const QFileInfo pfi = project()->projectFilePath().toFileInfo();
    const QStringList filter(pfi.fileName() + suffix + QLatin1Char('*'));
989

990 991 992 993 994 995
    if (!sharedUserFileDir().isEmpty()) {
        const QString sharedPath = sharedUserFileDir() + QLatin1Char('/')
            + makeRelative(pfi.absolutePath());
        list.append(QDir(sharedPath).entryInfoList(filter, QDir::Files));
    }
    list.append(QDir(pfi.dir()).entryInfoList(filter, QDir::Files));
996

997
    foreach (const QFileInfo &fi, list) {
998
        const Utils::FileName path = Utils::FileName::fromString(fi.absoluteFilePath());
999 1000 1001 1002 1003 1004
        if (!result.contains(path)) {
            if (path.endsWith(suffix))
                result.prepend(path);
            else
                result.append(path);
        }
1005
    }
1006

1007 1008 1009
    return result;
}

1010
QByteArray SettingsAccessor::creatorId()
1011
{
1012
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
1013 1014 1015 1016
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
1017
    return userFilePath(project(), suffix).toString();
1018 1019 1020 1021
}

int SettingsAccessor::currentVersion() const
{
1022 1023 1024 1025 1026 1027
    return d->currentVersion();
}

int SettingsAccessor::firstSupportedVersion() const
{
    return d->firstVersion();
1028 1029
}

1030
Utils::FileName SettingsAccessor::backupName(const QVariantMap &data) const
1031
{
1032 1033
    QString backupName = defaultFileName(m_userSuffix);
    const QByteArray oldEnvironmentId = environmentIdFromMap(data);
1034
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
1035
        backupName += QLatin1Char('.') + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
1036
    const int oldVersion = versionFromMap(data);
1037
    if (oldVersion != currentVersion()) {
1038 1039
        VersionUpgrader *upgrader = d->upgrader(oldVersion);
        if (upgrader)
1040
            backupName += QLatin1Char('.') + upgrader->backupExtension();
1041
        else
1042
            backupName += QLatin1Char('.') + QString::number(oldVersion);
1043
    }
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
    return Utils::FileName::fromString(backupName);
}

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

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

1062
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
1063
{
1064
    SettingsAccessorPrivate::Settings result;
1065
    QList<Utils::FileName> fileList = settingsFiles(m_userSuffix);
1066
    if (fileList.isEmpty()) // No settings found at all.
1067
        return result.map;
1068

1069
    result = d->bestSettings(this, fileList);
1070 1071 1072 1073 1074 1075
    if (result.path.isEmpty())
        result.path = project()->projectDirectory();

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

1077
    return result.map;
1078 1079
}

1080
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const