LinkManager.cc 20.3 KB
Newer Older
pixhawk's avatar
pixhawk committed
1
/*=====================================================================
lm's avatar
lm committed
2 3 4

QGroundControl Open Source Ground Control Station

5
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
lm's avatar
lm committed
6 7 8 9

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
pixhawk's avatar
pixhawk committed
10 11 12
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
lm's avatar
lm committed
13 14

    QGROUNDCONTROL is distributed in the hope that it will be useful,
pixhawk's avatar
pixhawk committed
15 16 17
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
lm's avatar
lm committed
18

pixhawk's avatar
pixhawk committed
19
    You should have received a copy of the GNU General Public License
lm's avatar
lm committed
20 21
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

pixhawk's avatar
pixhawk committed
22
======================================================================*/
23

pixhawk's avatar
pixhawk committed
24 25 26 27 28 29 30 31 32 33
/**
 * @file
 *   @brief Brief Description
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QList>
#include <QApplication>
34
#include <QDebug>
dogmaphobic's avatar
dogmaphobic committed
35 36

#ifndef __ios__
dogmaphobic's avatar
dogmaphobic committed
37 38 39
#ifdef __android__
#include "qserialportinfo.h"
#else
40
#include <QSerialPortInfo>
dogmaphobic's avatar
dogmaphobic committed
41
#endif
dogmaphobic's avatar
dogmaphobic committed
42
#endif
43

44 45
#include "LinkManager.h"
#include "MainWindow.h"
Don Gagne's avatar
Don Gagne committed
46
#include "QGCMessageBox.h"
47
#include "QGCApplication.h"
48
#include "SerialPortIds.h"
49
#include "QGCApplication.h"
50

Don Gagne's avatar
Don Gagne committed
51
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
52

53 54
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
55 56 57
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
58
    , _mavlinkChannelsUsedBitMask(0)
59
    , _nullSharedLink(NULL)
60
    , _mavlinkProtocol(NULL)
pixhawk's avatar
pixhawk committed
61
{
62

pixhawk's avatar
pixhawk committed
63 64 65 66
}

LinkManager::~LinkManager()
{
67 68 69 70 71 72
    // Clear configuration list
    while(_linkConfigurations.count()) {
        LinkConfiguration* pLink = _linkConfigurations.at(0);
        if(pLink) delete pLink;
        _linkConfigurations.removeAt(0);
    }
73
    Q_ASSERT_X(_links.count() == 0, "LinkManager", "LinkManager::_shutdown should have been called previously");
pixhawk's avatar
pixhawk committed
74 75
}

76 77 78 79 80 81 82 83 84 85 86 87
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

#ifndef __ios__
    connect(&_portListTimer, &QTimer::timeout, this, &LinkManager::_updateConfigurationList);
    _portListTimer.start(1000);
#endif
}

88
LinkInterface* LinkManager::createConnectedLink(LinkConfiguration* config)
89 90 91 92
{
    Q_ASSERT(config);
    LinkInterface* pLink = NULL;
    switch(config->type()) {
dogmaphobic's avatar
dogmaphobic committed
93
#ifndef __ios__
94 95 96
        case LinkConfiguration::TypeSerial:
            pLink = new SerialLink(dynamic_cast<SerialConfiguration*>(config));
            break;
dogmaphobic's avatar
dogmaphobic committed
97
#endif
98 99 100
        case LinkConfiguration::TypeUdp:
            pLink = new UDPLink(dynamic_cast<UDPConfiguration*>(config));
            break;
101 102 103
        case LinkConfiguration::TypeTcp:
            pLink = new TCPLink(dynamic_cast<TCPConfiguration*>(config));
            break;
Don Gagne's avatar
Don Gagne committed
104 105 106
        case LinkConfiguration::TypeLogReplay:
            pLink = new LogReplayLink(dynamic_cast<LogReplayLinkConfiguration*>(config));
            break;
107
#ifdef QT_DEBUG
108 109 110
        case LinkConfiguration::TypeMock:
            pLink = new MockLink(dynamic_cast<MockConfiguration*>(config));
            break;
111
#endif
112 113
    }
    if(pLink) {
114 115
        _addLink(pLink);
        connectLink(pLink);
116 117 118 119
    }
    return pLink;
}

120
LinkInterface* LinkManager::createConnectedLink(const QString& name)
121 122 123 124 125
{
    Q_ASSERT(name.isEmpty() == false);
    for(int i = 0; i < _linkConfigurations.count(); i++) {
        LinkConfiguration* conf = _linkConfigurations.at(i);
        if(conf && conf->name() == name)
126
            return createConnectedLink(conf);
127 128 129 130
    }
    return NULL;
}

131
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
132
{
133
    Q_ASSERT(link);
134

135
    _linkListMutex.lock();
136

137
    if (!containsLink(link)) {
138 139 140
        // Find a mavlink channel to use for this link
        for (int i=0; i<32; i++) {
            if (!(_mavlinkChannelsUsedBitMask && 1 << i)) {
141
                mavlink_reset_channel_status(i);
142 143 144 145 146 147
                link->_setMavlinkChannel(i);
                _mavlinkChannelsUsedBitMask |= i << i;
                break;
            }
        }
        
148
        _links.append(QSharedPointer<LinkInterface>(link));
149
        _linkListMutex.unlock();
150
        emit newLink(link);
151
    } else {
152
        _linkListMutex.unlock();
153
    }
154

155 156
    // MainWindow may be around when doing things like running unit tests
    if (MainWindow::instance()) {
157
        connect(link, &LinkInterface::communicationError, _app, &QGCApplication::criticalMessageBoxOnMainThread);
158
    }
159

160 161 162 163
    connect(link, &LinkInterface::bytesReceived,    _mavlinkProtocol, &MAVLinkProtocol::receiveBytes);
    connect(link, &LinkInterface::connected,        _mavlinkProtocol, &MAVLinkProtocol::linkConnected);
    connect(link, &LinkInterface::disconnected,     _mavlinkProtocol, &MAVLinkProtocol::linkDisconnected);
    _mavlinkProtocol->resetMetadataForLink(link);
164

165
    connect(link, &LinkInterface::connected,    this, &LinkManager::_linkConnected);
166
    connect(link, &LinkInterface::disconnected, this, &LinkManager::_linkDisconnected);
167
}
pixhawk's avatar
pixhawk committed
168 169 170

bool LinkManager::connectAll()
{
171 172 173
    if (_connectionsSuspendedMsg()) {
        return false;
    }
174

175 176
    bool allConnected = true;

177 178 179
    foreach (SharedLinkInterface sharedLink, _links) {
        Q_ASSERT(sharedLink.data());
        if (!sharedLink.data()->_connect()) {
180 181
            allConnected = false;
        }
182 183 184
    }

    return allConnected;
pixhawk's avatar
pixhawk committed
185 186 187 188
}

bool LinkManager::disconnectAll()
{
189 190
    bool allDisconnected = true;

191 192 193 194
    // Make a copy so the list is modified out from under us
    QList<SharedLinkInterface> links = _links;

    foreach (SharedLinkInterface sharedLink, links) {
195
        Q_ASSERT(sharedLink.data());
196
        if (!disconnectLink(sharedLink.data())) {
197 198
            allDisconnected = false;
        }
199 200 201
    }

    return allDisconnected;
pixhawk's avatar
pixhawk committed
202 203 204 205
}

bool LinkManager::connectLink(LinkInterface* link)
{
206
    Q_ASSERT(link);
207

208 209 210 211
    if (_connectionsSuspendedMsg()) {
        return false;
    }

212 213 214 215 216
    if (link->_connect()) {
        return true;
    } else {
        return false;
    }
pixhawk's avatar
pixhawk committed
217 218 219 220
}

bool LinkManager::disconnectLink(LinkInterface* link)
{
221
    Q_ASSERT(link);
222
    if (link->_disconnect()) {
223 224 225 226
        LinkConfiguration* config = link->getLinkConfiguration();
        if(config) {
            config->setLink(NULL);
        }
227
        _deleteLink(link);
228 229 230 231
        return true;
    } else {
        return false;
    }
pixhawk's avatar
pixhawk committed
232 233
}

234
void LinkManager::_deleteLink(LinkInterface* link)
235
{
236
    Q_ASSERT(link);
237

238
    _linkListMutex.lock();
239 240 241
    
    // Free up the mavlink channel associated with this link
    _mavlinkChannelsUsedBitMask &= ~(1 << link->getMavlinkChannel());
242

243 244 245 246 247 248 249 250
    bool found = false;
    for (int i=0; i<_links.count(); i++) {
        if (_links[i].data() == link) {
            _links.removeAt(i);
            found = true;
            break;
        }
    }
251
    Q_UNUSED(found);
252
    Q_ASSERT(found);
253

254
    _linkListMutex.unlock();
255

Don Gagne's avatar
Don Gagne committed
256
    // Emit removal of link
257
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
258 259 260 261 262 263 264
}

/**
 *
 */
const QList<LinkInterface*> LinkManager::getLinks()
{
265 266 267 268
    QList<LinkInterface*> list;
    
    foreach (SharedLinkInterface sharedLink, _links) {
        list << sharedLink.data();
269
    }
270 271
    
    return list;
272
}
273 274 275 276 277 278

/// @brief If all new connections should be suspended a message is displayed to the user and true
///         is returned;
bool LinkManager::_connectionsSuspendedMsg(void)
{
    if (_connectionsSuspended) {
Don Gagne's avatar
Don Gagne committed
279 280
        QGCMessageBox::information(tr("Connect not allowed"),
                                   tr("Connect not allowed: %1").arg(_connectionsSuspendedReason));
281 282 283 284 285 286 287 288 289 290 291 292
        return true;
    } else {
        return false;
    }
}

void LinkManager::setConnectionsSuspended(QString reason)
{
    _connectionsSuspended = true;
    _connectionsSuspendedReason = reason;
    Q_ASSERT(!reason.isEmpty());
}
293 294 295

void LinkManager::_shutdown(void)
{
296 297
    while (_links.count() != 0) {
        disconnectLink(_links[0].data());
298 299
    }
}
300 301 302 303 304 305 306 307 308 309

void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

void LinkManager::_linkDisconnected(void)
{
    emit linkDisconnected((LinkInterface*)sender());
}
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349

void LinkManager::addLinkConfiguration(LinkConfiguration* link)
{
    Q_ASSERT(link != NULL);
    //-- If not there already, add it
    int idx = _linkConfigurations.indexOf(link);
    if(idx < 0)
    {
        _linkConfigurations.append(link);
    }
}

void LinkManager::removeLinkConfiguration(LinkConfiguration *link)
{
    Q_ASSERT(link != NULL);
    int idx = _linkConfigurations.indexOf(link);
    if(idx >= 0)
    {
        _linkConfigurations.removeAt(idx);
        delete link;
    }
}

const QList<LinkConfiguration*> LinkManager::getLinkConfigurationList()
{
    return _linkConfigurations;
}

void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
    int index = 0;
    foreach (LinkConfiguration* pLink, _linkConfigurations) {
        Q_ASSERT(pLink != NULL);
dogmaphobic's avatar
dogmaphobic committed
350 351 352 353 354 355 356 357 358 359
        if(!pLink->isDynamic())
        {
            QString root = LinkConfiguration::settingsRoot();
            root += QString("/Link%1").arg(index++);
            settings.setValue(root + "/name", pLink->name());
            settings.setValue(root + "/type", pLink->type());
            settings.setValue(root + "/preferred", pLink->isPreferred());
            // Have the instance save its own values
            pLink->saveSettings(settings, root);
        }
360
    }
dogmaphobic's avatar
dogmaphobic committed
361
    QString root(LinkConfiguration::settingsRoot());
362
    settings.setValue(root + "/count", index);
363 364 365 366 367
    emit linkConfigurationChanged();
}

void LinkManager::loadLinkConfigurationList()
{
368 369
    bool udpExists = false;
    bool linksChanged = false;
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    QSettings settings;
    // Is the group even there?
    if(settings.contains(LinkConfiguration::settingsRoot() + "/count")) {
        // Find out how many configurations we have
        int count = settings.value(LinkConfiguration::settingsRoot() + "/count").toInt();
        for(int i = 0; i < count; i++) {
            QString root(LinkConfiguration::settingsRoot());
            root += QString("/Link%1").arg(i);
            if(settings.contains(root + "/type")) {
                int type = settings.value(root + "/type").toInt();
                if(type < LinkConfiguration::TypeLast) {
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
                            bool preferred = false;
                            if(settings.contains(root + "/preferred")) {
                                preferred = settings.value(root + "/preferred").toBool();
                            }
                            LinkConfiguration* pLink = NULL;
                            switch(type) {
dogmaphobic's avatar
dogmaphobic committed
390
#ifndef __ios__
391 392 393 394
                                case LinkConfiguration::TypeSerial:
                                    pLink = (LinkConfiguration*)new SerialConfiguration(name);
                                    pLink->setPreferred(preferred);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
395
#endif
396 397 398 399
                                case LinkConfiguration::TypeUdp:
                                    pLink = (LinkConfiguration*)new UDPConfiguration(name);
                                    pLink->setPreferred(preferred);
                                    break;
400 401 402 403
                                case LinkConfiguration::TypeTcp:
                                    pLink = (LinkConfiguration*)new TCPConfiguration(name);
                                    pLink->setPreferred(preferred);
                                    break;
Don Gagne's avatar
Don Gagne committed
404 405 406 407
                                case LinkConfiguration::TypeLogReplay:
                                    pLink = (LinkConfiguration*)new LogReplayLinkConfiguration(name);
                                    pLink->setPreferred(preferred);
                                    break;
408
#ifdef QT_DEBUG
409 410 411 412
                                case LinkConfiguration::TypeMock:
                                    pLink = (LinkConfiguration*)new MockConfiguration(name);
                                    pLink->setPreferred(false);
                                    break;
413
#endif
414 415 416 417 418
                            }
                            if(pLink) {
                                // Have the instance load its own values
                                pLink->loadSettings(settings, root);
                                addLinkConfiguration(pLink);
419 420 421 422 423 424 425 426
                                linksChanged = true;
                                // Check for UDP links
                                if(pLink->type() == LinkConfiguration::TypeUdp) {
                                    UDPConfiguration* uLink = dynamic_cast<UDPConfiguration*>(pLink);
                                    if(uLink && uLink->localPort() == QGC_UDP_LOCAL_PORT) {
                                        udpExists = true;
                                    }
                                }
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
                            }
                        } else {
                            qWarning() << "Link Configuration " << root << " has an empty name." ;
                        }
                    } else {
                        qWarning() << "Link Configuration " << root << " has no name." ;
                    }
                } else {
                    qWarning() << "Link Configuration " << root << " an invalid type: " << type;
                }
            } else {
                qWarning() << "Link Configuration " << root << " has no type." ;
            }
        }
    }
442 443 444
    
    // Debug buids always add MockLink automatically
#ifdef QT_DEBUG
445
    MockConfiguration* pMock = new MockConfiguration("Mock Link PX4");
446 447
    pMock->setDynamic(true);
    addLinkConfiguration(pMock);
448
    linksChanged = true;
449
#endif
450 451 452 453 454 455 456 457 458

    //-- If we don't have a configured UDP link, create a default one
    if(!udpExists) {
        UDPConfiguration* uLink = new UDPConfiguration("Default UDP Link");
        uLink->setLocalPort(QGC_UDP_LOCAL_PORT);
        uLink->setDynamic();
        addLinkConfiguration(uLink);
        linksChanged = true;
    }
459
    
460 461 462 463
    if(linksChanged) {
        emit linkConfigurationChanged();
    }
    // Enable automatic Serial PX4/3DR Radio hunting
464 465 466
    _configurationsLoaded = true;
}

dogmaphobic's avatar
dogmaphobic committed
467
#ifndef __ios__
468 469 470 471 472 473 474 475 476 477 478 479 480 481
SerialConfiguration* LinkManager::_findSerialConfiguration(const QString& portName)
{
    QString searchPort = portName.trimmed();
    foreach (LinkConfiguration* pLink, _linkConfigurations) {
        Q_ASSERT(pLink != NULL);
        if(pLink->type() == LinkConfiguration::TypeSerial) {
            SerialConfiguration* pSerial = dynamic_cast<SerialConfiguration*>(pLink);
            if(pSerial->portName() == searchPort) {
                return pSerial;
            }
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
482
#endif
483

dogmaphobic's avatar
dogmaphobic committed
484
#ifndef __ios__
485 486 487 488 489 490
void LinkManager::_updateConfigurationList(void)
{
    if (_configUpdateSuspended || !_configurationsLoaded) {
        return;
    }
    bool saveList = false;
dogmaphobic's avatar
dogmaphobic committed
491
    QStringList currentPorts;
492 493 494
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    // Iterate Comm Ports
    foreach (QSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
495 496
#if 0
        // Too noisy for most logging, so turn on as needed
Don Gagne's avatar
Don Gagne committed
497 498 499 500 501 502 503
        qCDebug(LinkManagerLog) << "-----------------------------------------------------";
        qCDebug(LinkManagerLog) << "portName:         " << portInfo.portName();
        qCDebug(LinkManagerLog) << "systemLocation:   " << portInfo.systemLocation();
        qCDebug(LinkManagerLog) << "description:      " << portInfo.description();
        qCDebug(LinkManagerLog) << "manufacturer:     " << portInfo.manufacturer();
        qCDebug(LinkManagerLog) << "serialNumber:     " << portInfo.serialNumber();
        qCDebug(LinkManagerLog) << "vendorIdentifier: " << portInfo.vendorIdentifier();
Don Gagne's avatar
Don Gagne committed
504
#endif
dogmaphobic's avatar
dogmaphobic committed
505 506
        // Save port name
        currentPorts << portInfo.systemLocation();
507
        // Is this a PX4 and NOT in bootloader mode?
508
        if (portInfo.vendorIdentifier() == SerialPortIds::px4VendorId && !portInfo.description().contains("BL")) {
509
            SerialConfiguration* pSerial = _findSerialConfiguration(portInfo.systemLocation());
510 511 512 513 514 515 516 517
            if (pSerial) {
                //-- If this port is configured make sure it has the preferred flag set
                if(!pSerial->isPreferred()) {
                    pSerial->setPreferred(true);
                    saveList = true;
                }
            } else {
                // Lets create a new Serial configuration automatically
dogmaphobic's avatar
dogmaphobic committed
518 519
                if (portInfo.description() == "AeroCore") {
                    pSerial = new SerialConfiguration(QString("AeroCore on %1").arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
520 521
                } else if (portInfo.description().contains("PX4Flow")) {
                    pSerial = new SerialConfiguration(QString("PX4Flow on %1").arg(portInfo.portName().trimmed()));
dogmaphobic's avatar
dogmaphobic committed
522 523 524 525 526
                } else if (portInfo.description().contains("PX4")) {
                    pSerial = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
                } else {
                    continue;
                }
dogmaphobic's avatar
dogmaphobic committed
527
                pSerial->setDynamic(true);
528 529
                pSerial->setPreferred(true);
                pSerial->setBaud(115200);
530
                pSerial->setPortName(portInfo.systemLocation());
531 532 533 534
                addLinkConfiguration(pSerial);
                saveList = true;
            }
        }
dogmaphobic's avatar
dogmaphobic committed
535
        // Is this an FTDI Chip? It could be a 3DR Modem
536
        if (portInfo.vendorIdentifier() == SerialPortIds::threeDRRadioVendorId && portInfo.productIdentifier() == SerialPortIds::threeDRRadioProductId) {
dogmaphobic's avatar
dogmaphobic committed
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
            SerialConfiguration* pSerial = _findSerialConfiguration(portInfo.systemLocation());
            if (pSerial) {
                //-- If this port is configured make sure it has the preferred flag set, unless someone else already has it set.
                if(!pSerial->isPreferred() && !saveList) {
                    pSerial->setPreferred(true);
                    saveList = true;
                }
            } else {
                // Lets create a new Serial configuration automatically (an assumption at best)
                pSerial = new SerialConfiguration(QString("3DR Radio on %1").arg(portInfo.portName().trimmed()));
                pSerial->setDynamic(true);
                pSerial->setPreferred(true);
                pSerial->setBaud(57600);
                pSerial->setPortName(portInfo.systemLocation());
                addLinkConfiguration(pSerial);
                saveList = true;
            }
        }
    }
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
    foreach (LinkConfiguration* pLink, _linkConfigurations) {
        Q_ASSERT(pLink != NULL);
        // We only care about dynamic links
        if(pLink->isDynamic()) {
            if(pLink->type() == LinkConfiguration::TypeSerial) {
563 564 565 566 567 568
                // Don't mess with connected link. Let it deal with the disapearing device.
                if(pLink->getLink() == NULL) {
                    SerialConfiguration* pSerial = dynamic_cast<SerialConfiguration*>(pLink);
                    if(!currentPorts.contains(pSerial->portName())) {
                        _confToDelete.append(pSerial);
                    }
dogmaphobic's avatar
dogmaphobic committed
569 570 571 572 573 574 575 576
                }
            }
        }
    }
    // Now remove all links that are gone
    foreach (LinkConfiguration* pDelete, _confToDelete) {
        removeLinkConfiguration(pDelete);
        saveList = true;
577 578 579 580 581 582
    }
    // Save configuration list, which will also trigger a signal for the UI
    if(saveList) {
        saveLinkConfigurationList();
    }
}
dogmaphobic's avatar
dogmaphobic committed
583
#endif
584

585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
bool LinkManager::containsLink(LinkInterface* link)
{
    bool found = false;
    foreach (SharedLinkInterface sharedLink, _links) {
        if (sharedLink.data() == link) {
            found = true;
            break;
        }
    }
    return found;
}

bool LinkManager::anyConnectedLinks(void)
{
    bool found = false;
    foreach (SharedLinkInterface sharedLink, _links) {
        if (sharedLink.data()->isConnected()) {
            found = true;
            break;
        }
    }
    return found;
}

SharedLinkInterface& LinkManager::sharedPointerForLink(LinkInterface* link)
{
    for (int i=0; i<_links.count(); i++) {
        if (_links[i].data() == link) {
            return _links[i];
        }
    }
    // This should never happen
    Q_ASSERT(false);
    return _nullSharedLink;
}