1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "devicemanager.h"
27 
28 #include "idevicefactory.h"
29 
30 #include <coreplugin/icore.h>
31 #include <coreplugin/messagemanager.h>
32 
33 #include <projectexplorer/projectexplorerconstants.h>
34 #include <utils/algorithm.h>
35 #include <utils/fileutils.h>
36 #include <utils/persistentsettings.h>
37 #include <utils/portlist.h>
38 #include <utils/qtcassert.h>
39 #include <utils/qtcprocess.h>
40 #include <utils/stringutils.h>
41 
42 #include <QDateTime>
43 #include <QFileInfo>
44 #include <QHash>
45 #include <QList>
46 #include <QString>
47 #include <QVariantList>
48 
49 #include <limits>
50 #include <memory>
51 
52 using namespace Utils;
53 
54 namespace ProjectExplorer {
55 namespace Internal {
56 
57 const char DeviceManagerKey[] = "DeviceManager";
58 const char DeviceListKey[] = "DeviceList";
59 const char DefaultDevicesKey[] = "DefaultDevices";
60 
61 class DeviceManagerPrivate
62 {
63 public:
64     DeviceManagerPrivate() = default;
65 
indexForId(Utils::Id id) const66     int indexForId(Utils::Id id) const
67     {
68         for (int i = 0; i < devices.count(); ++i) {
69             if (devices.at(i)->id() == id)
70                 return i;
71         }
72         return -1;
73     }
74 
75     static DeviceManager *clonedInstance;
76     QList<IDevice::Ptr> devices;
77     QHash<Utils::Id, Utils::Id> defaultDevices;
78 
79     Utils::PersistentSettingsWriter *writer = nullptr;
80 };
81 DeviceManager *DeviceManagerPrivate::clonedInstance = nullptr;
82 
83 } // namespace Internal
84 
85 using namespace Internal;
86 
87 DeviceManager *DeviceManager::m_instance = nullptr;
88 
instance()89 DeviceManager *DeviceManager::instance()
90 {
91     return m_instance;
92 }
93 
deviceCount() const94 int DeviceManager::deviceCount() const
95 {
96     return d->devices.count();
97 }
98 
replaceInstance()99 void DeviceManager::replaceInstance()
100 {
101     const QList<Id> newIds =
102         Utils::transform(DeviceManagerPrivate::clonedInstance->d->devices, &IDevice::id);
103 
104     for (IDevice::Ptr dev : m_instance->d->devices) {
105         if (!newIds.contains(dev->id()))
106             dev->aboutToBeRemoved();
107     }
108 
109     copy(DeviceManagerPrivate::clonedInstance, instance(), false);
110 
111     emit instance()->deviceListReplaced();
112     emit instance()->updated();
113 }
114 
removeClonedInstance()115 void DeviceManager::removeClonedInstance()
116 {
117     delete DeviceManagerPrivate::clonedInstance;
118     DeviceManagerPrivate::clonedInstance = nullptr;
119 }
120 
cloneInstance()121 DeviceManager *DeviceManager::cloneInstance()
122 {
123     QTC_ASSERT(!DeviceManagerPrivate::clonedInstance, return nullptr);
124 
125     DeviceManagerPrivate::clonedInstance = new DeviceManager(false);
126     copy(instance(), DeviceManagerPrivate::clonedInstance, true);
127     return DeviceManagerPrivate::clonedInstance;
128 }
129 
copy(const DeviceManager * source,DeviceManager * target,bool deep)130 void DeviceManager::copy(const DeviceManager *source, DeviceManager *target, bool deep)
131 {
132     if (deep) {
133         foreach (const IDevice::ConstPtr &device, source->d->devices)
134             target->d->devices << device->clone();
135     } else {
136         target->d->devices = source->d->devices;
137     }
138     target->d->defaultDevices = source->d->defaultDevices;
139 }
140 
save()141 void DeviceManager::save()
142 {
143     if (d->clonedInstance == this || !d->writer)
144         return;
145     QVariantMap data;
146     data.insert(QLatin1String(DeviceManagerKey), toMap());
147     d->writer->save(data, Core::ICore::dialogParent());
148 }
149 
load()150 void DeviceManager::load()
151 {
152     QTC_ASSERT(!d->writer, return);
153 
154     // Only create writer now: We do not want to save before the settings were read!
155     d->writer = new PersistentSettingsWriter(settingsFilePath("devices.xml"), "QtCreatorDevices");
156 
157     Utils::PersistentSettingsReader reader;
158     // read devices file from global settings path
159     QHash<Utils::Id, Utils::Id> defaultDevices;
160     QList<IDevice::Ptr> sdkDevices;
161     if (reader.load(systemSettingsFilePath("devices.xml")))
162         sdkDevices = fromMap(reader.restoreValues().value(DeviceManagerKey).toMap(), &defaultDevices);
163     // read devices file from user settings path
164     QList<IDevice::Ptr> userDevices;
165     if (reader.load(settingsFilePath("devices.xml")))
166         userDevices = fromMap(reader.restoreValues().value(DeviceManagerKey).toMap(), &defaultDevices);
167     // Insert devices into the model. Prefer the higher device version when there are multiple
168     // devices with the same id.
169     foreach (IDevice::Ptr device, userDevices) {
170         foreach (const IDevice::Ptr &sdkDevice, sdkDevices) {
171             if (device->id() == sdkDevice->id()) {
172                 if (device->version() < sdkDevice->version())
173                     device = sdkDevice;
174                 sdkDevices.removeOne(sdkDevice);
175                 break;
176             }
177         }
178         addDevice(device);
179     }
180     // Append the new SDK devices to the model.
181     foreach (const IDevice::Ptr &sdkDevice, sdkDevices)
182         addDevice(sdkDevice);
183 
184     // Overwrite with the saved default devices.
185     for (auto itr = defaultDevices.constBegin(); itr != defaultDevices.constEnd(); ++itr) {
186         IDevice::ConstPtr device = find(itr.value());
187         if (device)
188             d->defaultDevices[device->type()] = device->id();
189     }
190 
191     emit devicesLoaded();
192 }
193 
fromMap(const QVariantMap & map,QHash<Utils::Id,Utils::Id> * defaultDevices)194 QList<IDevice::Ptr> DeviceManager::fromMap(const QVariantMap &map,
195                                            QHash<Utils::Id, Utils::Id> *defaultDevices)
196 {
197     QList<IDevice::Ptr> devices;
198 
199     if (defaultDevices) {
200         const QVariantMap defaultDevsMap = map.value(DefaultDevicesKey).toMap();
201         for (auto it = defaultDevsMap.constBegin(); it != defaultDevsMap.constEnd(); ++it)
202             defaultDevices->insert(Utils::Id::fromString(it.key()), Utils::Id::fromSetting(it.value()));
203     }
204     const QVariantList deviceList = map.value(QLatin1String(DeviceListKey)).toList();
205     foreach (const QVariant &v, deviceList) {
206         const QVariantMap map = v.toMap();
207         const IDeviceFactory * const factory = restoreFactory(map);
208         if (!factory)
209             continue;
210         const IDevice::Ptr device = factory->construct();
211         QTC_ASSERT(device, continue);
212         device->fromMap(map);
213         devices << device;
214     }
215     return devices;
216 }
217 
toMap() const218 QVariantMap DeviceManager::toMap() const
219 {
220     QVariantMap map;
221     QVariantMap defaultDeviceMap;
222     using TypeIdHash = QHash<Utils::Id, Utils::Id>;
223     for (TypeIdHash::ConstIterator it = d->defaultDevices.constBegin();
224              it != d->defaultDevices.constEnd(); ++it) {
225         defaultDeviceMap.insert(it.key().toString(), it.value().toSetting());
226     }
227     map.insert(QLatin1String(DefaultDevicesKey), defaultDeviceMap);
228     QVariantList deviceList;
229     foreach (const IDevice::ConstPtr &device, d->devices)
230         deviceList << device->toMap();
231     map.insert(QLatin1String(DeviceListKey), deviceList);
232     return map;
233 }
234 
settingsFilePath(const QString & extension)235 FilePath DeviceManager::settingsFilePath(const QString &extension)
236 {
237     return Core::ICore::userResourcePath(extension);
238 }
239 
systemSettingsFilePath(const QString & deviceFileRelativePath)240 FilePath DeviceManager::systemSettingsFilePath(const QString &deviceFileRelativePath)
241 {
242     return Core::ICore::installerResourcePath(deviceFileRelativePath);
243 }
244 
addDevice(const IDevice::ConstPtr & _device)245 void DeviceManager::addDevice(const IDevice::ConstPtr &_device)
246 {
247     const IDevice::Ptr device = _device->clone();
248 
249     QStringList names;
250     foreach (const IDevice::ConstPtr &tmp, d->devices) {
251         if (tmp->id() != device->id())
252             names << tmp->displayName();
253     }
254 
255     device->setDisplayName(Utils::makeUniquelyNumbered(device->displayName(), names));
256 
257     const int pos = d->indexForId(device->id());
258 
259     if (!defaultDevice(device->type()))
260         d->defaultDevices.insert(device->type(), device->id());
261     if (this == DeviceManager::instance() && d->clonedInstance)
262         d->clonedInstance->addDevice(device->clone());
263 
264     if (pos >= 0) {
265         d->devices[pos] = device;
266         emit deviceUpdated(device->id());
267     } else {
268         d->devices << device;
269         emit deviceAdded(device->id());
270     }
271 
272     emit updated();
273 }
274 
removeDevice(Utils::Id id)275 void DeviceManager::removeDevice(Utils::Id id)
276 {
277     const IDevice::Ptr device = mutableDevice(id);
278     QTC_ASSERT(device, return);
279     QTC_ASSERT(this != instance() || device->isAutoDetected(), return);
280 
281     const bool wasDefault = d->defaultDevices.value(device->type()) == device->id();
282     const Utils::Id deviceType = device->type();
283     d->devices.removeAt(d->indexForId(id));
284     emit deviceRemoved(device->id());
285 
286     if (wasDefault) {
287         for (int i = 0; i < d->devices.count(); ++i) {
288             if (deviceAt(i)->type() == deviceType) {
289                 d->defaultDevices.insert(deviceAt(i)->type(), deviceAt(i)->id());
290                 emit deviceUpdated(deviceAt(i)->id());
291                 break;
292             }
293         }
294     }
295     if (this == instance() && d->clonedInstance)
296         d->clonedInstance->removeDevice(id);
297 
298     emit updated();
299 }
300 
setDeviceState(Utils::Id deviceId,IDevice::DeviceState deviceState)301 void DeviceManager::setDeviceState(Utils::Id deviceId, IDevice::DeviceState deviceState)
302 {
303     // To see the state change in the DeviceSettingsWidget. This has to happen before
304     // the pos check below, in case the device is only present in the cloned instance.
305     if (this == instance() && d->clonedInstance)
306         d->clonedInstance->setDeviceState(deviceId, deviceState);
307 
308     const int pos = d->indexForId(deviceId);
309     if (pos < 0)
310         return;
311     IDevice::Ptr &device = d->devices[pos];
312     if (device->deviceState() == deviceState)
313         return;
314 
315     device->setDeviceState(deviceState);
316     emit deviceUpdated(deviceId);
317     emit updated();
318 }
319 
isLoaded() const320 bool DeviceManager::isLoaded() const
321 {
322     return d->writer;
323 }
324 
deviceForPath(const FilePath & path)325 IDevice::ConstPtr DeviceManager::deviceForPath(const FilePath &path)
326 {
327     for (IDevice::Ptr &dev : instance()->d->devices) {
328         if (dev->handlesFile(path))
329             return dev;
330     }
331     return {};
332 }
333 
defaultDesktopDevice()334 IDevice::ConstPtr DeviceManager::defaultDesktopDevice()
335 {
336     return m_instance->defaultDevice(Constants::DESKTOP_DEVICE_TYPE);
337 }
338 
setDefaultDevice(Utils::Id id)339 void DeviceManager::setDefaultDevice(Utils::Id id)
340 {
341     QTC_ASSERT(this != instance(), return);
342 
343     const IDevice::ConstPtr &device = find(id);
344     QTC_ASSERT(device, return);
345     const IDevice::ConstPtr &oldDefaultDevice = defaultDevice(device->type());
346     if (device == oldDefaultDevice)
347         return;
348     d->defaultDevices.insert(device->type(), device->id());
349     emit deviceUpdated(device->id());
350     emit deviceUpdated(oldDefaultDevice->id());
351 
352     emit updated();
353 }
354 
restoreFactory(const QVariantMap & map)355 const IDeviceFactory *DeviceManager::restoreFactory(const QVariantMap &map)
356 {
357     const Utils::Id deviceType = IDevice::typeFromMap(map);
358     IDeviceFactory *factory = Utils::findOrDefault(IDeviceFactory::allDeviceFactories(),
359         [&map, deviceType](IDeviceFactory *factory) {
360             return factory->canRestore(map) && factory->deviceType() == deviceType;
361         });
362 
363     if (!factory)
364         qWarning("Warning: No factory found for device '%s' of type '%s'.",
365                  qPrintable(IDevice::idFromMap(map).toString()),
366                  qPrintable(IDevice::typeFromMap(map).toString()));
367     return factory;
368 }
369 
DeviceManager(bool isInstance)370 DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManagerPrivate>())
371 {
372     QTC_ASSERT(isInstance == !m_instance, return);
373 
374     if (!isInstance)
375         return;
376 
377     m_instance = this;
378     connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested,
379             this, &DeviceManager::save);
380 
381     DeviceFileHooks deviceHooks;
382 
383     deviceHooks.isExecutableFile = [](const FilePath &filePath) {
384         auto device = DeviceManager::deviceForPath(filePath);
385         QTC_ASSERT(device, return false);
386         return device->isExecutableFile(filePath);
387     };
388 
389     deviceHooks.isReadableFile = [](const FilePath &filePath) {
390         auto device = DeviceManager::deviceForPath(filePath);
391         QTC_ASSERT(device, return false);
392         return device->isReadableFile(filePath);
393     };
394 
395     deviceHooks.isReadableDir = [](const FilePath &filePath) {
396         auto device = DeviceManager::deviceForPath(filePath);
397         QTC_ASSERT(device, return false);
398         return device->isReadableDirectory(filePath);
399     };
400 
401     deviceHooks.isWritableDir = [](const FilePath &filePath) {
402         auto device = DeviceManager::deviceForPath(filePath);
403         QTC_ASSERT(device, return false);
404         return device->isWritableDirectory(filePath);
405     };
406 
407     deviceHooks.isWritableFile = [](const FilePath &filePath) {
408         auto device = DeviceManager::deviceForPath(filePath);
409         QTC_ASSERT(device, return false);
410         return device->isWritableFile(filePath);
411     };
412 
413     deviceHooks.isFile = [](const FilePath &filePath) {
414         auto device = DeviceManager::deviceForPath(filePath);
415         QTC_ASSERT(device, return false);
416         return device->isFile(filePath);
417     };
418 
419     deviceHooks.isDir = [](const FilePath &filePath) {
420         auto device = DeviceManager::deviceForPath(filePath);
421         QTC_ASSERT(device, return false);
422         return device->isDirectory(filePath);
423     };
424 
425     deviceHooks.ensureWritableDir = [](const FilePath &filePath) {
426         auto device = DeviceManager::deviceForPath(filePath);
427         QTC_ASSERT(device, return false);
428         return device->ensureWritableDirectory(filePath);
429     };
430 
431     deviceHooks.ensureExistingFile = [](const FilePath &filePath) {
432         auto device = DeviceManager::deviceForPath(filePath);
433         QTC_ASSERT(device, return false);
434         return device->ensureExistingFile(filePath);
435     };
436 
437     deviceHooks.createDir = [](const FilePath &filePath) {
438         auto device = DeviceManager::deviceForPath(filePath);
439         QTC_ASSERT(device, return false);
440         return device->createDirectory(filePath);
441     };
442 
443     deviceHooks.exists = [](const FilePath &filePath) {
444         auto device = DeviceManager::deviceForPath(filePath);
445         QTC_ASSERT(device, return false);
446         return device->exists(filePath);
447     };
448 
449     deviceHooks.removeFile = [](const FilePath &filePath) {
450         auto device = DeviceManager::deviceForPath(filePath);
451         QTC_ASSERT(device, return false);
452         return device->removeFile(filePath);
453     };
454 
455     deviceHooks.removeRecursively = [](const FilePath &filePath) {
456         auto device = DeviceManager::deviceForPath(filePath);
457         QTC_ASSERT(device, return false);
458         return device->removeRecursively(filePath);
459     };
460 
461     deviceHooks.copyFile = [](const FilePath &filePath, const FilePath &target) {
462         auto device = DeviceManager::deviceForPath(filePath);
463         QTC_ASSERT(device, return false);
464         return device->copyFile(filePath, target);
465     };
466 
467     deviceHooks.renameFile = [](const FilePath &filePath, const FilePath &target) {
468         auto device = DeviceManager::deviceForPath(filePath);
469         QTC_ASSERT(device, return false);
470         return device->renameFile(filePath, target);
471     };
472 
473     deviceHooks.searchInPath = [](const FilePath &filePath, const FilePaths &dirs) {
474         auto device = DeviceManager::deviceForPath(filePath);
475         QTC_ASSERT(device, return FilePath{});
476         return device->searchExecutable(filePath.path(), dirs);
477     };
478 
479     deviceHooks.symLinkTarget = [](const FilePath &filePath) {
480         auto device = DeviceManager::deviceForPath(filePath);
481         QTC_ASSERT(device, return FilePath{});
482         return device->symLinkTarget(filePath);
483     };
484 
485     deviceHooks.dirEntries = [](const FilePath &filePath, const QStringList &nameFilters,
486                                 QDir::Filters filters, QDir::SortFlags sort) {
487         auto device = DeviceManager::deviceForPath(filePath);
488         QTC_ASSERT(device, return FilePaths());
489         return device->directoryEntries(filePath, nameFilters, filters, sort);
490     };
491 
492     deviceHooks.fileContents = [](const FilePath &filePath, qint64 maxSize, qint64 offset) {
493         auto device = DeviceManager::deviceForPath(filePath);
494         QTC_ASSERT(device, return QByteArray());
495         return device->fileContents(filePath, maxSize, offset);
496     };
497 
498     deviceHooks.writeFileContents = [](const FilePath &filePath, const QByteArray &data) {
499         auto device = DeviceManager::deviceForPath(filePath);
500         QTC_ASSERT(device, return false);
501         return device->writeFileContents(filePath, data);
502     };
503 
504     deviceHooks.lastModified = [](const FilePath &filePath) {
505         auto device = DeviceManager::deviceForPath(filePath);
506         QTC_ASSERT(device, return QDateTime());
507         return device->lastModified(filePath);
508     };
509 
510     deviceHooks.permissions = [](const FilePath &filePath) {
511         auto device = DeviceManager::deviceForPath(filePath);
512         QTC_ASSERT(device, return QFile::Permissions());
513         return device->permissions(filePath);
514     };
515 
516     deviceHooks.osType = [](const FilePath &filePath) {
517         auto device = DeviceManager::deviceForPath(filePath);
518         QTC_ASSERT(device, return OsTypeOther);
519         return device->osType();
520     };
521 
522     deviceHooks.environment = [](const FilePath &filePath) {
523         auto device = DeviceManager::deviceForPath(filePath);
524         QTC_ASSERT(device, return Environment{});
525         return device->systemEnvironment();
526     };
527 
528     FileUtils::setDeviceFileHooks(deviceHooks);
529 
530     DeviceProcessHooks processHooks;
531 
532     processHooks.startProcessHook = [](QtcProcess &process) {
533         FilePath filePath = process.commandLine().executable();
534         auto device = DeviceManager::deviceForPath(filePath);
535         QTC_ASSERT(device, return);
536         device->runProcess(process);
537     };
538 
539     processHooks.systemEnvironmentForBinary = [](const FilePath &filePath) {
540         auto device = DeviceManager::deviceForPath(filePath);
541         QTC_ASSERT(device, return Environment());
542         return device->systemEnvironment();
543     };
544 
545     QtcProcess::setRemoteProcessHooks(processHooks);
546 }
547 
~DeviceManager()548 DeviceManager::~DeviceManager()
549 {
550     if (d->clonedInstance != this)
551         delete d->writer;
552     if (m_instance == this)
553         m_instance = nullptr;
554 }
555 
deviceAt(int idx) const556 IDevice::ConstPtr DeviceManager::deviceAt(int idx) const
557 {
558     QTC_ASSERT(idx >= 0 && idx < deviceCount(), return IDevice::ConstPtr());
559     return d->devices.at(idx);
560 }
561 
mutableDevice(Utils::Id id) const562 IDevice::Ptr DeviceManager::mutableDevice(Utils::Id id) const
563 {
564     const int index = d->indexForId(id);
565     return index == -1 ? IDevice::Ptr() : d->devices.at(index);
566 }
567 
hasDevice(const QString & name) const568 bool DeviceManager::hasDevice(const QString &name) const
569 {
570     return Utils::anyOf(d->devices, [&name](const IDevice::Ptr &device) {
571         return device->displayName() == name;
572     });
573 }
574 
find(Utils::Id id) const575 IDevice::ConstPtr DeviceManager::find(Utils::Id id) const
576 {
577     const int index = d->indexForId(id);
578     return index == -1 ? IDevice::ConstPtr() : deviceAt(index);
579 }
580 
defaultDevice(Utils::Id deviceType) const581 IDevice::ConstPtr DeviceManager::defaultDevice(Utils::Id deviceType) const
582 {
583     const Utils::Id id = d->defaultDevices.value(deviceType);
584     return id.isValid() ? find(id) : IDevice::ConstPtr();
585 }
586 
587 } // namespace ProjectExplorer
588 
589 
590 #ifdef WITH_TESTS
591 #include <projectexplorer/projectexplorer.h>
592 #include <QSignalSpy>
593 #include <QTest>
594 #include <QUuid>
595 
596 namespace ProjectExplorer {
597 
598 class TestDevice : public IDevice
599 {
600 public:
TestDevice()601     TestDevice()
602     {
603         setupId(AutoDetected, Utils::Id::fromString(QUuid::createUuid().toString()));
604         setType(testTypeId());
605         setMachineType(Hardware);
606         setOsType(HostOsInfo::hostOs());
607         setDisplayType("blubb");
608     }
609 
testTypeId()610     static Utils::Id testTypeId() { return "TestType"; }
611 private:
createWidget()612     IDeviceWidget *createWidget() override { return nullptr; }
signalOperation() const613     DeviceProcessSignalOperation::Ptr signalOperation() const override
614     {
615         return DeviceProcessSignalOperation::Ptr();
616     }
617 };
618 
619 class TestDeviceFactory final : public IDeviceFactory
620 {
621 public:
TestDeviceFactory()622     TestDeviceFactory() : IDeviceFactory(TestDevice::testTypeId())
623     {
624         setConstructionFunction([] { return IDevice::Ptr(new TestDevice); });
625     }
626 };
627 
testDeviceManager()628 void ProjectExplorerPlugin::testDeviceManager()
629 {
630     TestDeviceFactory factory;
631 
632     TestDevice::Ptr dev = IDevice::Ptr(new TestDevice);
633     dev->setDisplayName(QLatin1String("blubbdiblubbfurz!"));
634     QVERIFY(dev->isAutoDetected());
635     QCOMPARE(dev->deviceState(), IDevice::DeviceStateUnknown);
636     QCOMPARE(dev->type(), TestDevice::testTypeId());
637 
638     TestDevice::Ptr dev2 = dev->clone();
639     QCOMPARE(dev->id(), dev2->id());
640 
641     DeviceManager * const mgr = DeviceManager::instance();
642     QVERIFY(!mgr->find(dev->id()));
643     const int oldDeviceCount = mgr->deviceCount();
644 
645     QSignalSpy deviceAddedSpy(mgr, &DeviceManager::deviceAdded);
646     QSignalSpy deviceRemovedSpy(mgr, &DeviceManager::deviceRemoved);
647     QSignalSpy deviceUpdatedSpy(mgr, &DeviceManager::deviceUpdated);
648     QSignalSpy deviceListReplacedSpy(mgr, &DeviceManager::deviceListReplaced);
649     QSignalSpy updatedSpy(mgr, &DeviceManager::updated);
650 
651     mgr->addDevice(dev);
652     QCOMPARE(mgr->deviceCount(), oldDeviceCount + 1);
653     QVERIFY(mgr->find(dev->id()));
654     QVERIFY(mgr->hasDevice(dev->displayName()));
655     QCOMPARE(deviceAddedSpy.count(), 1);
656     QCOMPARE(deviceRemovedSpy.count(), 0);
657     QCOMPARE(deviceUpdatedSpy.count(), 0);
658     QCOMPARE(deviceListReplacedSpy.count(), 0);
659     QCOMPARE(updatedSpy.count(), 1);
660     deviceAddedSpy.clear();
661     updatedSpy.clear();
662 
663     mgr->setDeviceState(dev->id(), IDevice::DeviceStateUnknown);
664     QCOMPARE(deviceAddedSpy.count(), 0);
665     QCOMPARE(deviceRemovedSpy.count(), 0);
666     QCOMPARE(deviceUpdatedSpy.count(), 0);
667     QCOMPARE(deviceListReplacedSpy.count(), 0);
668     QCOMPARE(updatedSpy.count(), 0);
669 
670     mgr->setDeviceState(dev->id(), IDevice::DeviceReadyToUse);
671     QCOMPARE(mgr->find(dev->id())->deviceState(), IDevice::DeviceReadyToUse);
672     QCOMPARE(deviceAddedSpy.count(), 0);
673     QCOMPARE(deviceRemovedSpy.count(), 0);
674     QCOMPARE(deviceUpdatedSpy.count(), 1);
675     QCOMPARE(deviceListReplacedSpy.count(), 0);
676     QCOMPARE(updatedSpy.count(), 1);
677     deviceUpdatedSpy.clear();
678     updatedSpy.clear();
679 
680     mgr->addDevice(dev2);
681     QCOMPARE(mgr->deviceCount(), oldDeviceCount + 1);
682     QVERIFY(mgr->find(dev->id()));
683     QCOMPARE(deviceAddedSpy.count(), 0);
684     QCOMPARE(deviceRemovedSpy.count(), 0);
685     QCOMPARE(deviceUpdatedSpy.count(), 1);
686     QCOMPARE(deviceListReplacedSpy.count(), 0);
687     QCOMPARE(updatedSpy.count(), 1);
688     deviceUpdatedSpy.clear();
689     updatedSpy.clear();
690 
691     TestDevice::Ptr dev3 = IDevice::Ptr(new TestDevice);
692     QVERIFY(dev->id() != dev3->id());
693 
694     dev3->setDisplayName(dev->displayName());
695     mgr->addDevice(dev3);
696     QCOMPARE(mgr->deviceAt(mgr->deviceCount() - 1)->displayName(),
697              QString(dev3->displayName() + QLatin1Char('2')));
698     QCOMPARE(deviceAddedSpy.count(), 1);
699     QCOMPARE(deviceRemovedSpy.count(), 0);
700     QCOMPARE(deviceUpdatedSpy.count(), 0);
701     QCOMPARE(deviceListReplacedSpy.count(), 0);
702     QCOMPARE(updatedSpy.count(), 1);
703     deviceAddedSpy.clear();
704     updatedSpy.clear();
705 
706     mgr->removeDevice(dev->id());
707     mgr->removeDevice(dev3->id());
708     QCOMPARE(mgr->deviceCount(), oldDeviceCount);
709     QVERIFY(!mgr->find(dev->id()));
710     QVERIFY(!mgr->find(dev3->id()));
711     QCOMPARE(deviceAddedSpy.count(), 0);
712     QCOMPARE(deviceRemovedSpy.count(), 2);
713 //    QCOMPARE(deviceUpdatedSpy.count(), 0); Uncomment once the "default" stuff is gone.
714     QCOMPARE(deviceListReplacedSpy.count(), 0);
715     QCOMPARE(updatedSpy.count(), 2);
716 }
717 
718 } // namespace ProjectExplorer
719 
720 #endif // WITH_TESTS
721