pluginspec.cpp 30.6 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
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
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "pluginspec.h"
31

con's avatar
con committed
32 33 34 35 36
#include "pluginspec_p.h"
#include "iplugin.h"
#include "iplugin_p.h"
#include "pluginmanager.h"

37 38 39 40 41 42
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QXmlStreamReader>
#include <QRegExp>
#include <QCoreApplication>
hjk's avatar
hjk committed
43
#include <QDebug>
con's avatar
con committed
44

45
#ifdef Q_OS_LINUX
46 47
// Using the patched version breaks on Fedora 10, KDE4.2.2/Qt4.5.
#   define USE_UNPATCHED_QPLUGINLOADER 1
48 49 50
#else
#   define USE_UNPATCHED_QPLUGINLOADER 1
#endif
51 52 53

#if USE_UNPATCHED_QPLUGINLOADER

54
#   include <QPluginLoader>
55
    typedef QT_PREPEND_NAMESPACE(QPluginLoader) PluginLoader;
56 57 58

#else

59 60
#   include "patchedpluginloader.cpp"
    typedef PatchedPluginLoader PluginLoader;
61 62 63

#endif

con's avatar
con committed
64 65
/*!
    \class ExtensionSystem::PluginDependency
66 67
    \brief The PluginDependency class contains the name and required compatible
    version number of a plugin's dependency.
con's avatar
con committed
68

69 70 71
    This reflects the data of a dependency tag in the plugin's XML description
    file. The name and version are used to resolve the dependency. That is,
    a plugin with the given name and
con's avatar
con committed
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
    plugin \c {compatibility version <= dependency version <= plugin version} is searched for.

    See also ExtensionSystem::IPlugin for more information about plugin dependencies and
    version matching.
*/

/*!
    \variable ExtensionSystem::PluginDependency::name
    String identifier of the plugin.
*/

/*!
    \variable ExtensionSystem::PluginDependency::version
    Version string that a plugin must match to fill this dependency.
*/

con's avatar
con committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
/*!
    \variable ExtensionSystem::PluginDependency::type
    Defines whether the dependency is required or optional.
    \sa ExtensionSystem::PluginDependency::Type
*/

/*!
    \enum ExtensionSystem::PluginDependency::Type
    Whether the dependency is required or optional.
    \value Required
           Dependency needs to be there.
    \value Optional
           Dependency is not necessarily needed. You need to make sure that
           the plugin is able to load without this dependency installed, so
           for example you may not link to the dependency's library.
*/

con's avatar
con committed
105 106
/*!
    \class ExtensionSystem::PluginSpec
107 108
    \brief The PluginSpec class contains the information of the plugin's XML
    description file and
con's avatar
con committed
109 110 111
    information about the plugin's current state.

    The plugin spec is also filled with more information as the plugin
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
112
    goes through its loading process (see PluginSpec::State).
con's avatar
con committed
113 114 115 116 117 118
    If an error occurs, the plugin spec is the place to look for the
    error details.
*/

/*!
    \enum ExtensionSystem::PluginSpec::State
119 120
    The State enum indicates the states the plugin goes through while
    it is being loaded.
con's avatar
con committed
121 122 123 124

    The state gives a hint on what went wrong in case of an error.

    \value  Invalid
125
            Starting point: Even the XML description file was not read.
con's avatar
con committed
126
    \value  Read
127
            The XML description file has been successfully read, and its
con's avatar
con committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
            information is available via the PluginSpec.
    \value  Resolved
            The dependencies given in the description file have been
            successfully found, and are available via the dependencySpecs() method.
    \value  Loaded
            The plugin's library is loaded and the plugin instance created
            (available through plugin()).
    \value  Initialized
            The plugin instance's IPlugin::initialize() method has been called
            and returned a success value.
    \value  Running
            The plugin's dependencies are successfully initialized and
            extensionsInitialized has been called. The loading process is
            complete.
    \value Stopped
143
            The plugin has been shut down, i.e. the plugin's IPlugin::aboutToShutdown() method has been called.
con's avatar
con committed
144 145 146
    \value Deleted
            The plugin instance has been deleted.
*/
con's avatar
con committed
147

con's avatar
con committed
148 149 150
using namespace ExtensionSystem;
using namespace ExtensionSystem::Internal;

con's avatar
con committed
151 152 153 154 155 156 157 158
/*!
    \internal
*/
uint ExtensionSystem::qHash(const ExtensionSystem::PluginDependency &value)
{
    return qHash(value.name);
}

con's avatar
con committed
159 160 161
/*!
    \internal
*/
con's avatar
con committed
162
bool PluginDependency::operator==(const PluginDependency &other) const
con's avatar
con committed
163
{
con's avatar
con committed
164
    return name == other.name && version == other.version && type == other.type;
con's avatar
con committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
}

/*!
    \internal
*/
PluginSpec::PluginSpec()
    : d(new PluginSpecPrivate(this))
{
}

/*!
    \internal
*/
PluginSpec::~PluginSpec()
{
    delete d;
    d = 0;
}

/*!
    The plugin name. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::name() const
{
    return d->name;
}

/*!
    The plugin version. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::version() const
{
    return d->version;
}

/*!
    The plugin compatibility version. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::compatVersion() const
{
    return d->compatVersion;
}

/*!
    The plugin vendor. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::vendor() const
{
    return d->vendor;
}

/*!
    The plugin copyright. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::copyright() const
{
    return d->copyright;
}

/*!
    The plugin license. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::license() const
{
    return d->license;
}

/*!
    The plugin description. This is valid after the PluginSpec::Read state is reached.
*/
QString PluginSpec::description() const
{
    return d->description;
}

/*!
241 242
    The plugin URL where you can find more information about the plugin.
    This is valid after the PluginSpec::Read state is reached.
con's avatar
con committed
243 244 245 246 247 248
*/
QString PluginSpec::url() const
{
    return d->url;
}

249 250 251 252 253 254 255 256 257
/*!
    The category that the plugin belongs to. Categories are groups of plugins which allow for keeping them together in the UI.
    Returns an empty string if the plugin does not belong to a category.
*/
QString PluginSpec::category() const
{
    return d->category;
}

258 259 260 261 262 263 264 265 266 267
/*!
    A QRegExp matching the platforms this plugin works on. An empty pattern implies all platforms.
    \since 3.0
*/

QRegExp PluginSpec::platformSpecification() const
{
    return d->platformSpecification;
}

268
/*!
269
    Returns whether the plugin has its experimental flag set.
270 271 272 273 274 275
*/
bool PluginSpec::isExperimental() const
{
    return d->experimental;
}

276
/*!
277
    Returns whether the plugin is disabled by default.
278 279 280 281 282 283 284 285
    This might be because the plugin is experimental, or because
    the plugin manager's settings define it as disabled by default.
*/
bool PluginSpec::isDisabledByDefault() const
{
    return d->disabledByDefault;
}

286
/*!
287 288
    Returns whether the plugin should be loaded at startup. True by default.

289
    The user can change it from the Plugin settings.
290 291 292

    \note This function returns true even if a plugin is disabled because its
    dependencies were not loaded, or an error occurred during loading it.
293
*/
294
bool PluginSpec::isEnabledInSettings() const
295
{
296
    return d->enabledInSettings;
297 298
}

299
/*!
300
    Returns whether the plugin is loaded at startup.
301 302 303 304
    \see PluginSpec::isEnabled
*/
bool PluginSpec::isEffectivelyEnabled() const
{
305 306 307 308 309 310
    if (d->disabledIndirectly
        || (!d->enabledInSettings && !d->forceEnabled)
        || d->forceDisabled) {
        return false;
    }
    return d->platformSpecification.isEmpty() || d->platformSpecification.exactMatch(PluginManager::platformName());
311 312
}

313
/*!
314
    Returns true if loading was not done due to user unselecting this plugin or its dependencies.
315 316
*/
bool PluginSpec::isDisabledIndirectly() const
317
{
318
    return d->disabledIndirectly;
319 320
}

321
/*!
322
    Returns whether the plugin is enabled via the -load option on the command line.
323 324 325 326 327 328 329
*/
bool PluginSpec::isForceEnabled() const
{
    return d->forceEnabled;
}

/*!
330
    Returns whether the plugin is disabled via the -noload option on the command line.
331 332 333 334 335 336
*/
bool PluginSpec::isForceDisabled() const
{
    return d->forceDisabled;
}

con's avatar
con committed
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
/*!
    The plugin dependencies. This is valid after the PluginSpec::Read state is reached.
*/
QList<PluginDependency> PluginSpec::dependencies() const
{
    return d->dependencies;
}

/*!
    Returns a list of descriptions of command line arguments the plugin processes.
*/

PluginSpec::PluginArgumentDescriptions PluginSpec::argumentDescriptions() const
{
    return d->argumentDescriptions;
}

/*!
355
    The absolute path to the directory containing the plugin XML description file
con's avatar
con committed
356 357 358 359 360 361 362 363
    this PluginSpec corresponds to.
*/
QString PluginSpec::location() const
{
    return d->location;
}

/*!
364
    The absolute path to the plugin XML description file (including the file name)
con's avatar
con committed
365 366 367 368 369 370 371 372
    this PluginSpec corresponds to.
*/
QString PluginSpec::filePath() const
{
    return d->filePath;
}

/*!
373
    Command line arguments specific to the plugin. Set at startup.
con's avatar
con committed
374 375 376 377 378 379 380 381
*/

QStringList PluginSpec::arguments() const
{
    return d->arguments;
}

/*!
382
    Sets the command line arguments specific to the plugin to \a arguments.
con's avatar
con committed
383 384 385 386 387 388 389 390
*/

void PluginSpec::setArguments(const QStringList &arguments)
{
    d->arguments = arguments;
}

/*!
391
    Adds \a argument to the command line arguments specific to the plugin.
con's avatar
con committed
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
*/

void PluginSpec::addArgument(const QString &argument)
{
    d->arguments.push_back(argument);
}


/*!
    The state in which the plugin currently is.
    See the description of the PluginSpec::State enum for details.
*/
PluginSpec::State PluginSpec::state() const
{
    return d->state;
}

/*!
    Returns whether an error occurred while reading/starting the plugin.
*/
bool PluginSpec::hasError() const
{
    return d->hasError;
}

/*!
    Detailed, possibly multi-line, error description in case of an error.
*/
QString PluginSpec::errorString() const
{
    return d->errorString;
}

/*!
426
    Returns whether this plugin can be used to fill in a dependency of the given
con's avatar
con committed
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    \a pluginName and \a version.

        \sa PluginSpec::dependencies()
*/
bool PluginSpec::provides(const QString &pluginName, const QString &version) const
{
    return d->provides(pluginName, version);
}

/*!
    The corresponding IPlugin instance, if the plugin library has already been successfully loaded,
    i.e. the PluginSpec::Loaded state is reached.
*/
IPlugin *PluginSpec::plugin() const
{
    return d->plugin;
}

/*!
    Returns the list of dependencies, already resolved to existing plugin specs.
    Valid if PluginSpec::Resolved state is reached.

    \sa PluginSpec::dependencies()
*/
con's avatar
con committed
451
QHash<PluginDependency, PluginSpec *> PluginSpec::dependencySpecs() const
con's avatar
con committed
452 453 454 455 456 457 458
{
    return d->dependencySpecs;
}

//==========PluginSpecPrivate==================

namespace {
459 460 461 462 463
    const char PLUGIN[] = "plugin";
    const char PLUGIN_NAME[] = "name";
    const char PLUGIN_VERSION[] = "version";
    const char PLUGIN_COMPATVERSION[] = "compatVersion";
    const char PLUGIN_EXPERIMENTAL[] = "experimental";
464
    const char PLUGIN_DISABLED_BY_DEFAULT[] = "disabledByDefault";
465 466 467 468 469 470
    const char VENDOR[] = "vendor";
    const char COPYRIGHT[] = "copyright";
    const char LICENSE[] = "license";
    const char DESCRIPTION[] = "description";
    const char URL[] = "url";
    const char CATEGORY[] = "category";
471
    const char PLATFORM[] = "platform";
472 473 474 475 476 477 478 479 480 481 482
    const char DEPENDENCYLIST[] = "dependencyList";
    const char DEPENDENCY[] = "dependency";
    const char DEPENDENCY_NAME[] = "name";
    const char DEPENDENCY_VERSION[] = "version";
    const char DEPENDENCY_TYPE[] = "type";
    const char DEPENDENCY_TYPE_SOFT[] = "optional";
    const char DEPENDENCY_TYPE_HARD[] = "required";
    const char ARGUMENTLIST[] = "argumentList";
    const char ARGUMENT[] = "argument";
    const char ARGUMENT_NAME[] = "name";
    const char ARGUMENT_PARAMETER[] = "parameter";
con's avatar
con committed
483 484 485 486 487
}
/*!
    \internal
*/
PluginSpecPrivate::PluginSpecPrivate(PluginSpec *spec)
488
    :
489 490
    experimental(false),
    disabledByDefault(false),
491
    enabledInSettings(true),
492
    disabledIndirectly(false),
493 494
    forceEnabled(false),
    forceDisabled(false),
495
    plugin(0),
con's avatar
con committed
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
    state(PluginSpec::Invalid),
    hasError(false),
    q(spec)
{
}

/*!
    \internal
*/
bool PluginSpecPrivate::read(const QString &fileName)
{
    name
        = version
        = compatVersion
        = vendor
        = copyright
        = license
        = description
        = url
515
        = category
con's avatar
con committed
516
        = location
517
        = QString();
con's avatar
con committed
518 519
    state = PluginSpec::Invalid;
    hasError = false;
520
    errorString.clear();
con's avatar
con committed
521 522 523
    dependencies.clear();
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
524
        return reportError(tr("Cannot open file %1 for reading: %2")
525
                           .arg(QDir::toNativeSeparators(file.fileName()), file.errorString()));
con's avatar
con committed
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
    QFileInfo fileInfo(file);
    location = fileInfo.absolutePath();
    filePath = fileInfo.absoluteFilePath();
    QXmlStreamReader reader(&file);
    while (!reader.atEnd()) {
        reader.readNext();
        switch (reader.tokenType()) {
        case QXmlStreamReader::StartElement:
            readPluginSpec(reader);
            break;
        default:
            break;
        }
    }
    if (reader.hasError())
        return reportError(tr("Error parsing file %1: %2, at line %3, column %4")
542
                .arg(QDir::toNativeSeparators(file.fileName()))
con's avatar
con committed
543 544 545 546 547 548 549
                .arg(reader.errorString())
                .arg(reader.lineNumber())
                .arg(reader.columnNumber()));
    state = PluginSpec::Read;
    return true;
}

con's avatar
con committed
550
void PluginSpec::setEnabled(bool value)
551
{
552
    d->enabledInSettings = value;
553 554
}

555 556 557 558 559
void PluginSpec::setDisabledByDefault(bool value)
{
    d->disabledByDefault = value;
}

560 561 562 563 564
void PluginSpec::setDisabledIndirectly(bool value)
{
    d->disabledIndirectly = value;
}

565 566 567 568 569 570 571 572 573 574 575 576 577 578
void PluginSpec::setForceEnabled(bool value)
{
    d->forceEnabled = value;
    if (value)
        d->forceDisabled = false;
}

void PluginSpec::setForceDisabled(bool value)
{
    if (value)
        d->forceEnabled = false;
    d->forceDisabled = value;
}

con's avatar
con committed
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
/*!
    \internal
*/
bool PluginSpecPrivate::reportError(const QString &err)
{
    errorString = err;
    hasError = true;
    return false;
}

static inline QString msgAttributeMissing(const char *elt, const char *attribute)
{
    return QCoreApplication::translate("PluginSpec", "'%1' misses attribute '%2'").arg(QLatin1String(elt), QLatin1String(attribute));
}

static inline QString msgInvalidFormat(const char *content)
{
596
    return QCoreApplication::translate("PluginSpec", "'%1' has invalid format").arg(QLatin1String(content));
con's avatar
con committed
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
}

static inline QString msgInvalidElement(const QString &name)
{
    return QCoreApplication::translate("PluginSpec", "Invalid element '%1'").arg(name);
}

static inline QString msgUnexpectedClosing(const QString &name)
{
    return QCoreApplication::translate("PluginSpec", "Unexpected closing element '%1'").arg(name);
}

static inline QString msgUnexpectedToken()
{
    return QCoreApplication::translate("PluginSpec", "Unexpected token");
}

/*!
    \internal
*/
void PluginSpecPrivate::readPluginSpec(QXmlStreamReader &reader)
{
619
    if (reader.name() != QLatin1String(PLUGIN)) {
620 621
        reader.raiseError(QCoreApplication::translate("PluginSpec", "Expected element '%1' as top level element")
                          .arg(QLatin1String(PLUGIN)));
con's avatar
con committed
622 623
        return;
    }
624
    name = reader.attributes().value(QLatin1String(PLUGIN_NAME)).toString();
con's avatar
con committed
625 626 627 628
    if (name.isEmpty()) {
        reader.raiseError(msgAttributeMissing(PLUGIN, PLUGIN_NAME));
        return;
    }
629
    version = reader.attributes().value(QLatin1String(PLUGIN_VERSION)).toString();
con's avatar
con committed
630 631 632 633 634 635 636 637
    if (version.isEmpty()) {
        reader.raiseError(msgAttributeMissing(PLUGIN, PLUGIN_VERSION));
        return;
    }
    if (!isValidVersion(version)) {
        reader.raiseError(msgInvalidFormat(PLUGIN_VERSION));
        return;
    }
638
    compatVersion = reader.attributes().value(QLatin1String(PLUGIN_COMPATVERSION)).toString();
con's avatar
con committed
639 640 641 642 643 644
    if (!compatVersion.isEmpty() && !isValidVersion(compatVersion)) {
        reader.raiseError(msgInvalidFormat(PLUGIN_COMPATVERSION));
        return;
    } else if (compatVersion.isEmpty()) {
        compatVersion = version;
    }
645 646 647
    disabledByDefault = readBooleanValue(reader, PLUGIN_DISABLED_BY_DEFAULT);
    experimental = readBooleanValue(reader, PLUGIN_EXPERIMENTAL);
    if (reader.hasError())
648
        return;
649 650
    if (experimental)
        disabledByDefault = true;
651
    enabledInSettings = !disabledByDefault;
con's avatar
con committed
652 653 654
    while (!reader.atEnd()) {
        reader.readNext();
        switch (reader.tokenType()) {
655 656
        case QXmlStreamReader::StartElement: {
            const QStringRef element = reader.name();
657
            if (element == QLatin1String(VENDOR))
con's avatar
con committed
658
                vendor = reader.readElementText().trimmed();
659
            else if (element == QLatin1String(COPYRIGHT))
con's avatar
con committed
660
                copyright = reader.readElementText().trimmed();
661
            else if (element == QLatin1String(LICENSE))
con's avatar
con committed
662
                license = reader.readElementText().trimmed();
663
            else if (element == QLatin1String(DESCRIPTION))
con's avatar
con committed
664
                description = reader.readElementText().trimmed();
665
            else if (element == QLatin1String(URL))
con's avatar
con committed
666
                url = reader.readElementText().trimmed();
667
            else if (element == QLatin1String(CATEGORY))
668
                category = reader.readElementText().trimmed();
669 670 671 672 673 674 675 676 677 678
            else if (element == QLatin1String(PLATFORM)) {
                const QString platformSpec = reader.readElementText().trimmed();
                if (!platformSpec.isEmpty()) {
                    platformSpecification.setPattern(platformSpec);
                    if (!platformSpecification.isValid())
                        reader.raiseError(QLatin1String("Invalid platform specification \"")
                                          + platformSpec + QLatin1String("\": ")
                                          + platformSpecification.errorString());
                }
            } else if (element == QLatin1String(DEPENDENCYLIST))
con's avatar
con committed
679
                readDependencies(reader);
680
            else if (element == QLatin1String(ARGUMENTLIST))
con's avatar
con committed
681 682
                readArgumentDescriptions(reader);
            else
683 684
                reader.raiseError(msgInvalidElement(element.toString()));
        }
con's avatar
con committed
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
            break;
        case QXmlStreamReader::EndDocument:
        case QXmlStreamReader::Comment:
        case QXmlStreamReader::EndElement:
        case QXmlStreamReader::Characters:
            break;
        default:
            reader.raiseError(msgUnexpectedToken());
            break;
        }
    }
}

/*!
    \internal
*/
void PluginSpecPrivate::readArgumentDescriptions(QXmlStreamReader &reader)
{
    while (!reader.atEnd()) {
        reader.readNext();
        switch (reader.tokenType()) {
        case QXmlStreamReader::StartElement:
707
            if (reader.name() == QLatin1String(ARGUMENT))
con's avatar
con committed
708
                readArgumentDescription(reader);
709
            else
710
                reader.raiseError(msgInvalidElement(reader.name().toString()));
con's avatar
con committed
711 712 713 714 715
            break;
        case QXmlStreamReader::Comment:
        case QXmlStreamReader::Characters:
            break;
        case QXmlStreamReader::EndElement:
716
            if (reader.name() == QLatin1String(ARGUMENTLIST))
con's avatar
con committed
717
                return;
718
            reader.raiseError(msgUnexpectedClosing(reader.name().toString()));
con's avatar
con committed
719 720 721 722 723 724 725 726 727 728 729 730 731 732
            break;
        default:
            reader.raiseError(msgUnexpectedToken());
            break;
        }
    }
}

/*!
    \internal
*/
void PluginSpecPrivate::readArgumentDescription(QXmlStreamReader &reader)
{
    PluginArgumentDescription arg;
733
    arg.name = reader.attributes().value(QLatin1String(ARGUMENT_NAME)).toString();
con's avatar
con committed
734 735 736 737
    if (arg.name.isEmpty()) {
        reader.raiseError(msgAttributeMissing(ARGUMENT, ARGUMENT_NAME));
        return;
    }
738
    arg.parameter = reader.attributes().value(QLatin1String(ARGUMENT_PARAMETER)).toString();
con's avatar
con committed
739 740 741 742 743 744
    arg.description = reader.readElementText();
    if (reader.tokenType() != QXmlStreamReader::EndElement)
        reader.raiseError(msgUnexpectedToken());
    argumentDescriptions.push_back(arg);
}

745 746
bool PluginSpecPrivate::readBooleanValue(QXmlStreamReader &reader, const char *key)
{
747
    const QStringRef valueString = reader.attributes().value(QLatin1String(key));
748 749 750 751 752 753 754 755
    const bool isOn = valueString.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0;
    if (!valueString.isEmpty() && !isOn
            && valueString.compare(QLatin1String("false"), Qt::CaseInsensitive) != 0) {
        reader.raiseError(msgInvalidFormat(key));
    }
    return isOn;
}

con's avatar
con committed
756 757 758 759 760 761 762 763 764
/*!
    \internal
*/
void PluginSpecPrivate::readDependencies(QXmlStreamReader &reader)
{
    while (!reader.atEnd()) {
        reader.readNext();
        switch (reader.tokenType()) {
        case QXmlStreamReader::StartElement:
765
            if (reader.name() == QLatin1String(DEPENDENCY))
con's avatar
con committed
766
                readDependencyEntry(reader);
767
            else
768
                reader.raiseError(msgInvalidElement(reader.name().toString()));
con's avatar
con committed
769 770 771 772 773
            break;
        case QXmlStreamReader::Comment:
        case QXmlStreamReader::Characters:
            break;
        case QXmlStreamReader::EndElement:
774
            if (reader.name() == QLatin1String(DEPENDENCYLIST))
con's avatar
con committed
775
                return;
776
            reader.raiseError(msgUnexpectedClosing(reader.name().toString()));
con's avatar
con committed
777 778 779 780 781 782 783 784 785 786 787 788 789 790
            break;
        default:
            reader.raiseError(msgUnexpectedToken());
            break;
        }
    }
}

/*!
    \internal
*/
void PluginSpecPrivate::readDependencyEntry(QXmlStreamReader &reader)
{
    PluginDependency dep;
791
    dep.name = reader.attributes().value(QLatin1String(DEPENDENCY_NAME)).toString();
con's avatar
con committed
792 793 794 795
    if (dep.name.isEmpty()) {
        reader.raiseError(msgAttributeMissing(DEPENDENCY, DEPENDENCY_NAME));
        return;
    }
796
    dep.version = reader.attributes().value(QLatin1String(DEPENDENCY_VERSION)).toString();
con's avatar
con committed
797 798 799 800
    if (!dep.version.isEmpty() && !isValidVersion(dep.version)) {
        reader.raiseError(msgInvalidFormat(DEPENDENCY_VERSION));
        return;
    }
con's avatar
con committed
801
    dep.type = PluginDependency::Required;
802
    if (reader.attributes().hasAttribute(QLatin1String(DEPENDENCY_TYPE))) {
803
        const QStringRef typeValue = reader.attributes().value(QLatin1String(DEPENDENCY_TYPE));
con's avatar
con committed
804 805 806 807 808 809 810 811 812
        if (typeValue == QLatin1String(DEPENDENCY_TYPE_HARD)) {
            dep.type = PluginDependency::Required;
        } else if (typeValue == QLatin1String(DEPENDENCY_TYPE_SOFT)) {
            dep.type = PluginDependency::Optional;
        } else {
            reader.raiseError(msgInvalidFormat(DEPENDENCY_TYPE));
            return;
        }
    }
con's avatar
con committed
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
    dependencies.append(dep);
    reader.readNext();
    if (reader.tokenType() != QXmlStreamReader::EndElement)
        reader.raiseError(msgUnexpectedToken());
}

/*!
    \internal
*/
bool PluginSpecPrivate::provides(const QString &pluginName, const QString &pluginVersion) const
{
    if (QString::compare(pluginName, name, Qt::CaseInsensitive) != 0)
        return false;
    return (versionCompare(version, pluginVersion) >= 0) && (versionCompare(compatVersion, pluginVersion) <= 0);
}

/*!
    \internal
*/
QRegExp &PluginSpecPrivate::versionRegExp()
{
834
    static QRegExp reg(QLatin1String("([0-9]+)(?:[.]([0-9]+))?(?:[.]([0-9]+))?(?:_([0-9]+))?"));
con's avatar
con committed
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
    return reg;
}
/*!
    \internal
*/
bool PluginSpecPrivate::isValidVersion(const QString &version)
{
    return versionRegExp().exactMatch(version);
}

/*!
    \internal
*/
int PluginSpecPrivate::versionCompare(const QString &version1, const QString &version2)
{
    QRegExp reg1 = versionRegExp();
    QRegExp reg2 = versionRegExp();
    if (!reg1.exactMatch(version1))
        return 0;
    if (!reg2.exactMatch(version2))
        return 0;
    int number1;
    int number2;
    for (int i = 0; i < 4; ++i) {
        number1 = reg1.cap(i+1).toInt();
        number2 = reg2.cap(i+1).toInt();
        if (number1 < number2)
            return -1;
        if (number1 > number2)
            return 1;
    }
    return 0;
}

/*!
    \internal
*/
872
bool PluginSpecPrivate::resolveDependencies(const QList<PluginSpec *> &specs)
con's avatar
con committed
873 874 875 876 877 878 879 880 881 882
{
    if (hasError)
        return false;
    if (state == PluginSpec::Resolved)
        state = PluginSpec::Read; // Go back, so we just re-resolve the dependencies.
    if (state != PluginSpec::Read) {
        errorString = QCoreApplication::translate("PluginSpec", "Resolving dependencies failed because state != Read");
        hasError = true;
        return false;
    }
con's avatar
con committed
883
    QHash<PluginDependency, PluginSpec *> resolvedDependencies;
con's avatar
con committed
884 885
    foreach (const PluginDependency &dependency, dependencies) {
        PluginSpec *found = 0;
886

con's avatar
con committed
887 888 889 890 891 892 893
        foreach (PluginSpec *spec, specs) {
            if (spec->provides(dependency.name, dependency.version)) {
                found = spec;
                break;
            }
        }
        if (!found) {
con's avatar
con committed
894 895 896 897 898 899 900
            if (dependency.type == PluginDependency::Required) {
                hasError = true;
                if (!errorString.isEmpty())
                    errorString.append(QLatin1Char('\n'));
                errorString.append(QCoreApplication::translate("PluginSpec", "Could not resolve dependency '%1(%2)'")
                    .arg(dependency.name).arg(dependency.version));
            }
con's avatar
con committed
901 902
            continue;
        }
con's avatar
con committed
903
        resolvedDependencies.insert(dependency, found);
con's avatar
con committed
904 905 906
    }
    if (hasError)
        return false;
907

con's avatar
con committed
908
    dependencySpecs = resolvedDependencies;
909

910
    state = PluginSpec::Resolved;
911

con's avatar
con committed
912 913 914
    return true;
}

915 916
void PluginSpecPrivate::disableIndirectlyIfDependencyDisabled()
{
917
    if (!enabledInSettings)
918 919
        return;

920 921 922
    if (disabledIndirectly)
        return;

con's avatar
con committed
923 924 925 926 927 928
    QHashIterator<PluginDependency, PluginSpec *> it(dependencySpecs);
    while (it.hasNext()) {
        it.next();
        if (it.key().type == PluginDependency::Optional)
            continue;
        PluginSpec *dependencySpec = it.value();
929
        if (!dependencySpec->isEffectivelyEnabled()) {
930 931 932 933 934 935
            disabledIndirectly = true;
            break;
        }
    }
}

con's avatar
con committed
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
/*!
    \internal
*/
bool PluginSpecPrivate::loadLibrary()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Resolved) {
        if (state == PluginSpec::Loaded)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Loading the library failed because state != Resolved");
        hasError = true;
        return false;
    }
#ifdef QT_NO_DEBUG

#ifdef Q_OS_WIN
953
    QString libName = QString::fromLatin1("%1/%2.dll").arg(location).arg(name);
con's avatar
con committed
954
#elif defined(Q_OS_MAC)
955
    QString libName = QString::fromLatin1("%1/lib%2.dylib").arg(location).arg(name);
con's avatar
con committed
956
#else
957
    QString libName = QString::fromLatin1("%1/lib%2.so").arg(location).arg(name);
con's avatar
con committed
958 959 960 961 962
#endif

#else //Q_NO_DEBUG

#ifdef Q_OS_WIN
963
    QString libName = QString::fromLatin1("%1/%2d.dll").arg(location).arg(name);
con's avatar
con committed
964
#elif defined(Q_OS_MAC)
965
    QString libName = QString::fromLatin1("%1/lib%2_debug.dylib").arg(location).arg(name);
con's avatar
con committed
966
#else
967
    QString libName = QString::fromLatin1("%1/lib%2.so").arg(location).arg(name);
con's avatar
con committed
968 969 970 971
#endif

#endif

972
    PluginLoader loader(libName);
con's avatar
con committed
973 974
    if (!loader.load()) {
        hasError = true;
Alessandro Portale's avatar
Alessandro Portale committed
975 976
        errorString = QDir::toNativeSeparators(libName)
            + QString::fromLatin1(": ") + loader.errorString();
con's avatar
con committed
977 978 979 980 981
        return false;
    }
    IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());
    if (!pluginObject) {
        hasError = true;
982
        errorString = QCoreApplication::translate("PluginSpec", "Plugin is not valid (does not derive from IPlugin)");
con's avatar
con committed
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
        loader.unload();
        return false;
    }
    state = PluginSpec::Loaded;
    plugin = pluginObject;
    plugin->d->pluginSpec = q;
    return true;
}

/*!
    \internal
*/
bool PluginSpecPrivate::initializePlugin()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Loaded) {
        if (state == PluginSpec::Initialized)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Initializing the plugin failed because state != Loaded");
        hasError = true;
        return false;
    }
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to initialize");
        hasError = true;
        return false;
    }
    QString err;
    if (!plugin->initialize(arguments, &err)) {
        errorString = QCoreApplication::translate("PluginSpec", "Plugin initialization failed: %1").arg(err);
        hasError = true;
        return false;
    }
    state = PluginSpec::Initialized;
    return true;
}

/*!
    \internal
*/
bool PluginSpecPrivate::initializeExtensions()
{
    if (hasError)
        return false;
    if (state != PluginSpec::Initialized) {
        if (state == PluginSpec::Running)
            return true;
        errorString = QCoreApplication::translate("PluginSpec", "Cannot perform extensionsInitialized because state != Initialized");
        hasError = true;
        return false;
    }
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to perform extensionsInitialized");
        hasError = true;
        return false;
    }
    plugin->extensionsInitialized();
    state = PluginSpec::Running;
    return true;
}

1045 1046 1047 1048 1049 1050 1051
/*!
    \internal
*/
bool PluginSpecPrivate::delayedInitialize()
{
    if (hasError)
        return false;
1052
    if (state != PluginSpec::Running)
1053 1054 1055 1056 1057 1058 1059 1060 1061
        return false;
    if (!plugin) {
        errorString = QCoreApplication::translate("PluginSpec", "Internal error: have no plugin instance to perform delayedInitialize");
        hasError = true;
        return false;
    }
    return plugin->delayedInitialize();
}

con's avatar
con committed
1062 1063 1064
/*!
    \internal
*/
1065
IPlugin::ShutdownFlag PluginSpecPrivate::stop()
con's avatar
con committed
1066 1067
{
    if (!plugin)
1068
        return IPlugin::SynchronousShutdown;
con's avatar
con committed
1069
    state = PluginSpec::Stopped;
1070
    return plugin->aboutToShutdown();
con's avatar
con committed
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
}

/*!
    \internal
*/
void PluginSpecPrivate::kill()
{
    if (!plugin)
        return;
    delete plugin;
    plugin = 0;
    state = PluginSpec::Deleted;
}