• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 32532906423

21 Aug 2026 10:25PM UTC coverage: 35.324%. Remained the same
32532906423

push

github

mcallegari
engine: improve loading of definitions without modes

8 of 20 new or added lines in 1 file covered. (40.0%)

18624 of 52723 relevant lines covered (35.32%)

40934.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.3
/engine/src/qlcfixturedefcache.cpp
1
/*
2
  Q Light Controller
3
  qlcfixturedefcache.cpp
4

5
  Copyright (c) Heikki Junnila
6

7
  Licensed under the Apache License, Version 2.0 (the "License");
8
  you may not use this file except in compliance with the License.
9
  You may obtain a copy of the License at
10

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

13
  Unless required by applicable law or agreed to in writing, software
14
  distributed under the License is distributed on an "AS IS" BASIS,
15
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
  See the License for the specific language governing permissions and
17
  limitations under the License.
18
*/
19

20
#include <QCoreApplication>
21
#include <QXmlStreamReader>
22
#include <QDebug>
23
#include <QList>
24
#include <QSet>
25

26
#if defined(WIN32) || defined(Q_OS_WIN)
27
#   include <windows.h>
28
#else
29
#   include <unistd.h>
30
#endif
31

32
#include "qlcfixturedefcache.h"
33
#include "avolitesd4parser.h"
34
#include "qlcfixturedef.h"
35
#include "qlcconfig.h"
36
#include "qlcfile.h"
37

38
#define FIXTURES_MAP_NAME QStringLiteral("FixturesMap.xml")
39
#define KXMLQLCFixtureMap QStringLiteral("FixturesMap")
40

41
QLCFixtureDefCache::QLCFixtureDefCache()
214 ✔
42
{
43
}
214 ✔
44

45
QLCFixtureDefCache::~QLCFixtureDefCache()
214 ✔
46
{
47
    clear();
214 ✔
48
}
214 ✔
49

50
QLCFixtureDef* QLCFixtureDefCache::fixtureDef(
220 ✔
51
    const QString& manufacturer, const QString& model) const
52
{
53
    QListIterator <QLCFixtureDef*> it(m_defs);
220 ✔
54
    while (it.hasNext() == true)
174,875 ✔
55
    {
56
        QLCFixtureDef* def = it.next();
174,852 ✔
57
        if (def->manufacturer() == manufacturer && def->model() == model)
174,852 ✔
58
        {
59
            def->checkLoaded(m_mapAbsolutePath);
197 ✔
60
            return def;
197 ✔
61
        }
62
    }
63

64
    return NULL;
23 ✔
65
}
220 ✔
66

67
QStringList QLCFixtureDefCache::manufacturers() const
38 ✔
68
{
69
    QSet <QString> makers;
38 ✔
70

71
    // Gather a list of manufacturers
72
    QListIterator <QLCFixtureDef*> it(m_defs);
38 ✔
73
    while (it.hasNext() == true)
34,781 ✔
74
        makers << it.next()->manufacturer();
34,743 ✔
75

76
    // Bounce the QSet into a QStringList
77
    QStringList list;
38 ✔
78
    foreach (QString manuf, makers)
2,929 ✔
79
        list << manuf;
2,929 ✔
80

81
    return list;
76 ✔
82
}
38 ✔
83

84
QStringList QLCFixtureDefCache::models(const QString& manufacturer) const
84,729 ✔
85
{
86
    QSet <QString> models;
84,729 ✔
87
    QListIterator <QLCFixtureDef*> it(m_defs);
84,729 ✔
88
    while (it.hasNext() == true)
74,773,039 ✔
89
    {
90
        QLCFixtureDef* def = it.next();
74,688,310 ✔
91
        if (def->manufacturer() == manufacturer)
74,688,310 ✔
92
            models << def->model();
2,833,267 ✔
93
    }
94

95
    // Bounce the QSet into a QStringList
96
    QStringList list;
84,729 ✔
97
    foreach (QString model, models)
2,917,996 ✔
98
        list << model;
2,917,996 ✔
99

100
    return list;
169,458 ✔
101
}
84,729 ✔
102

103
QMap<QString, QMap<QString, bool> > QLCFixtureDefCache::fixtureCache() const
2 ✔
104
{
105
    QMap<QString, QMap<QString, bool> > map;
2 ✔
106

107
    QListIterator <QLCFixtureDef*> it(m_defs);
2 ✔
108
    while (it.hasNext() == true)
3,472 ✔
109
    {
110
        QLCFixtureDef *def = it.next();
3,470 ✔
111
        map[def->manufacturer()][def->model()] = def->isUser();
3,470 ✔
112
    }
113

114
    return map;
4 ✔
115
}
2 ✔
116

117
bool QLCFixtureDefCache::addFixtureDef(QLCFixtureDef* fixtureDef)
83,292 ✔
118
{
119
    if (fixtureDef == NULL)
83,292 ✔
120
        return false;
1 ✔
121

122
    if (models(fixtureDef->manufacturer()).contains(fixtureDef->model()) == false)
83,291 ✔
123
    {
124
        m_defs << fixtureDef;
83,288 ✔
125
        return true;
83,288 ✔
126
    }
127
    else
128
    {
129
        qWarning() << Q_FUNC_INFO << "Cache already contains"
3 ✔
130
                   << fixtureDef->name();
3 ✔
131
        return false;
3 ✔
132
    }
133
}
134

135
bool QLCFixtureDefCache::storeFixtureDef(const QString& filename, const QString& data)
1 ✔
136
{
137
    QDir userFolder = userDefinitionDirectory();
1 ✔
138

139
    QFile file(userFolder.absoluteFilePath(filename));
1 ✔
140
    if (file.open(QIODevice::WriteOnly | QIODevice::Text) == false)
1 ✔
141
        return false;
×
142

143
    file.write(data.toUtf8());
1 ✔
144
    file.close();
1 ✔
145
#ifdef Q_OS_UNIX
146
    sync();
1 ✔
147
#endif
148

149
    // reload user definitions
150
    load(userDefinitionDirectory());
1 ✔
151

152
    return true;
1 ✔
153
}
1 ✔
154

155
bool QLCFixtureDefCache::reloadFixtureDef(QLCFixtureDef *fixtureDef)
1 ✔
156
{
157
    int idx = m_defs.indexOf(fixtureDef);
1 ✔
158
    if (idx == -1)
1 ✔
159
        return false;
×
160

161
    QLCFixtureDef *def = m_defs.takeAt(idx);
1 ✔
162
    QString absPath = def->definitionSourceFile();
1 ✔
163
    bool isUser = def->isUser();
1 ✔
164
    delete def;
1 ✔
165

166
    QLCFixtureDef *origDef = new QLCFixtureDef();
1 ✔
167
    QFile::FileError error = origDef->loadXML(absPath);
1 ✔
168
    if (error != QFile::NoError)
1 ✔
169
    {
NEW
170
        qWarning() << Q_FUNC_INFO << "Fixture definition loading from"
×
NEW
171
                   << absPath << "failed:" << QLCFile::errorString(error);
×
NEW
172
        delete origDef;
×
NEW
173
        return false;
×
174
    }
175

176
    /* A definition with no modes cannot be used to patch a fixture,
177
       so don't add it back to the cache. Note that fixture editors load
178
       definitions directly, so they can still handle incomplete ones */
179
    if (origDef->modes().isEmpty())
1 ✔
180
    {
NEW
181
        qWarning() << Q_FUNC_INFO << "Fixture definition" << absPath
×
NEW
182
                   << "has no modes. Skipping.";
×
NEW
183
        delete origDef;
×
NEW
184
        return false;
×
185
    }
186

187
    origDef->setIsUser(isUser);
1 ✔
188
    origDef->setDefinitionSourceFile(absPath);
1 ✔
189
    origDef->setLoaded(true);
1 ✔
190
    m_defs << origDef;
1 ✔
191

192
    return true;
1 ✔
193
}
1 ✔
194

195
bool QLCFixtureDefCache::reloadOrAddFixtureDef(QLCFixtureDef *fixtureDef)
×
196
{
197
    // Check existing definitions first.
198
    for (int i = 0; i < m_defs.size(); i++)
×
199
    {
200
        QLCFixtureDef *def = m_defs.at(i);
×
201
        if (def->manufacturer() == fixtureDef->manufacturer() &&
×
202
            def->model() == fixtureDef->model())
×
203
        {
204
            if (def == fixtureDef)
×
205
            {
206
                // Cache and editor must not share ownership of the same instance.
207
                // Keep a detached cache copy and leave editor-owned instance untouched.
208
                QLCFixtureDef *cacheCopy = new QLCFixtureDef(fixtureDef);
×
209
                cacheCopy->setIsUser(true);
×
210
                cacheCopy->setLoaded(true);
×
211
                m_defs[i] = cacheCopy;
×
212
            }
213
            else
214
            {
215
                // Set as user and perform a deep copy.
216
                def->setIsUser(true);
×
217
                *def = *fixtureDef;
×
218
                def->setLoaded(true);
×
219
            }
220

221
            return true;
×
222
        }
223
    }
224

225
    // Add a new user fixture as a detached cache copy.
226
    QLCFixtureDef *cacheCopy = new QLCFixtureDef(fixtureDef);
×
227
    cacheCopy->setIsUser(true);
×
228
    cacheCopy->setLoaded(true);
×
229
    if (addFixtureDef(cacheCopy) == false)
×
230
    {
231
        delete cacheCopy;
×
232
        return false;
×
233
    }
234

235
    return true;
×
236
}
237

238
bool QLCFixtureDefCache::load(const QDir& dir)
9 ✔
239
{
240
    qDebug() << Q_FUNC_INFO << dir.path();
9 ✔
241

242
    if (dir.exists() == false || dir.isReadable() == false)
9 ✔
243
        return false;
7 ✔
244

245
    /* Attempt to read all specified files from the given directory */
246
    QStringListIterator it(dir.entryList());
2 ✔
247
    while (it.hasNext() == true)
3 ✔
248
    {
249
        QString path(dir.absoluteFilePath(it.next()));
1 ✔
250

251
        if (path.toLower().endsWith(KExtFixture) == true)
2 ✔
252
            loadQXF(path, true);
1 ✔
253
        else if (path.toLower().endsWith(KExtAvolitesFixture) == true)
×
254
            loadD4(path);
×
255
        else
256
            qWarning() << Q_FUNC_INFO << "Unrecognized fixture extension:" << path;
×
257
    }
1 ✔
258

259
    return true;
2 ✔
260
}
2 ✔
261

262
int QLCFixtureDefCache::loadMapManufacturer(QXmlStreamReader *doc, const QString& manufacturer)
6,864 ✔
263
{
264
    int count = 0;
6,864 ✔
265
    QString spacedManufacturer = manufacturer;
6,864 ✔
266
    spacedManufacturer.replace("_", " ");
6,864 ✔
267

268
    while (doc->readNextStartElement())
90,144 ✔
269
    {
270
        if (doc->name() == QString("F"))
83,280 ✔
271
        {
272
            QString defFile = "";
83,280 ✔
273
            QString model = "";
83,280 ✔
274

275
            if (doc->attributes().hasAttribute("n"))
166,560 ✔
276
            {
277
                defFile = QString("%1%2%3%4")
166,560 ✔
278
                            .arg(manufacturer).arg(QDir::separator())
166,560 ✔
279
                            .arg(doc->attributes().value("n").toString()).arg(KExtFixture);
249,840 ✔
280
                //qDebug() << "Manufacturer" << spacedManufacturer << "file" << defFile;
281
            }
282

283
            if (doc->attributes().hasAttribute("m"))
166,560 ✔
284
                model = doc->attributes().value("m").toString();
166,560 ✔
285

286
            if (defFile.isEmpty() == false &&
83,280 ✔
287
                spacedManufacturer.isEmpty() == false &&
166,560 ✔
288
                model.isEmpty() == false)
83,280 ✔
289
            {
290
                QLCFixtureDef *fxi = new QLCFixtureDef();
83,280 ✔
291
                Q_ASSERT(fxi != NULL);
83,280 ✔
292

293
                fxi->setDefinitionSourceFile(defFile);
83,280 ✔
294
                fxi->setManufacturer(spacedManufacturer);
83,280 ✔
295
                fxi->setModel(model);
83,280 ✔
296

297
                /* Delete the def if it's a duplicate. */
298
                if (addFixtureDef(fxi) == false)
83,280 ✔
299
                    delete fxi;
×
300
                fxi = NULL;
83,280 ✔
301
                count++;
83,280 ✔
302
            }
303
        }
83,280 ✔
304
        else
305
        {
306
            qWarning() << Q_FUNC_INFO << "Unknown manufacturer tag: " << doc->name();
×
307
        }
308
        doc->skipCurrentElement();
83,280 ✔
309
    }
310

311
    return count;
6,864 ✔
312
}
6,864 ✔
313

314
bool QLCFixtureDefCache::loadMap(const QDir &dir)
48 ✔
315
{
316
    qDebug() << Q_FUNC_INFO << dir.path();
48 ✔
317

318
    if (dir.exists() == false || dir.isReadable() == false)
48 ✔
319
        return false;
×
320

321
    QString mapPath(dir.absoluteFilePath(FIXTURES_MAP_NAME));
96 ✔
322

323
    if (mapPath.isEmpty() == true)
48 ✔
324
        return false;
×
325

326
    // cache the map path to be used when composing the fixture
327
    // definition absolute path
328
    m_mapAbsolutePath = dir.absolutePath();
48 ✔
329

330
    QXmlStreamReader *doc = QLCFile::getXMLReader(mapPath);
48 ✔
331
    if (doc == NULL || doc->device() == NULL || doc->hasError())
48 ✔
332
    {
333
        qWarning() << Q_FUNC_INFO << "Unable to read from" << mapPath;
×
334
        return false;
×
335
    }
336

337
    while (!doc->atEnd())
96 ✔
338
    {
339
        if (doc->readNext() == QXmlStreamReader::DTD)
96 ✔
340
            break;
48 ✔
341
    }
342

343
    if (doc->hasError())
48 ✔
344
    {
345
        QLCFile::releaseXMLReader(doc);
×
346
        return false;
×
347
    }
348

349
    // make sure the doc type is FixtureMap
350
    if (doc->dtdName() != KXMLQLCFixtureMap)
48 ✔
351
    {
352
        qWarning() << Q_FUNC_INFO << mapPath << "is not a fixture map file";
×
353
        QLCFile::releaseXMLReader(doc);
×
354
        return false;
×
355
    }
356

357
    if (doc->readNextStartElement() == false)
48 ✔
358
    {
359
        QLCFile::releaseXMLReader(doc);
×
360
        return false;
×
361
    }
362

363
    // make sure the root tag is FixtureMap
364
    if (doc->name() != KXMLQLCFixtureMap)
48 ✔
365
    {
366
        qWarning() << Q_FUNC_INFO << mapPath << "is not a fixture map file";
×
367
        QLCFile::releaseXMLReader(doc);
×
368
        return false;
×
369
    }
370

371
    int fxCount = 0;
48 ✔
372
    QString manufacturer = "";
48 ✔
373

374
    while (doc->readNextStartElement())
6,912 ✔
375
    {
376
        if (doc->name() == QString("M"))
6,864 ✔
377
        {
378
            if (doc->attributes().hasAttribute("n"))
13,728 ✔
379
            {
380
                manufacturer = doc->attributes().value("n").toString();
13,728 ✔
381
                fxCount += loadMapManufacturer(doc, manufacturer);
6,864 ✔
382
            }
383
        }
384
        else
385
        {
386
            qWarning() << Q_FUNC_INFO << "Unknown Fixture Map tag: " << doc->name();
×
387
            doc->skipCurrentElement();
×
388
        }
389
    }
390
    qDebug() << fxCount << "fixtures found in map";
48 ✔
391

392
#if 0
393
    /* Attempt to read all files not in FixtureMap */
394
    QStringList definitionPaths;
395

396
    // Gather a list of manufacturers
397
    QListIterator <QLCFixtureDef*> mfit(m_defs);
398
    while (mfit.hasNext() == true)
399
        definitionPaths << mfit.next()->definitionSourceFile();
400

401
    QStringListIterator it(dir.entryList());
402
    while (it.hasNext() == true)
403
    {
404
        QString path(dir.absoluteFilePath(it.next()));
405
        if (definitionPaths.contains(path))
406
            continue;
407

408
        qWarning() << path << "not in" << FIXTURES_MAP_NAME;
409

410
        if (path.toLower().endsWith(KExtFixture) == true)
411
            loadQXF(path);
412
        else if (path.toLower().endsWith(KExtAvolitesFixture) == true)
413
            loadD4(path);
414
        else
415
            qWarning() << Q_FUNC_INFO << "Unrecognized fixture extension:" << path;
416
    }
417
#endif
418
    return true;
48 ✔
419
}
48 ✔
420

421
void QLCFixtureDefCache::clear()
223 ✔
422
{
423
    while (m_defs.isEmpty() == false)
83,511 ✔
424
        delete m_defs.takeFirst();
83,288 ✔
425
}
223 ✔
426

427
QDir QLCFixtureDefCache::systemDefinitionDirectory()
1 ✔
428
{
429
    return QLCFile::systemDirectory(QString(FIXTUREDIR), QString(KExtFixture));
2 ✔
430
}
431

432
QDir QLCFixtureDefCache::userDefinitionDirectory()
4 ✔
433
{
434
    QStringList filters;
4 ✔
435
    filters << QString("*%1").arg(KExtFixture);
8 ✔
436
    filters << QString("*%1").arg(KExtAvolitesFixture);
8 ✔
437

438
    return QLCFile::userDirectory(QString(USERFIXTUREDIR), QString(FIXTUREDIR), filters);
12 ✔
439
}
4 ✔
440

441
bool QLCFixtureDefCache::loadQXF(const QString& path, bool isUser)
10 ✔
442
{
443
    QLCFixtureDef *fxi = new QLCFixtureDef();
10 ✔
444
    Q_ASSERT(fxi != NULL);
10 ✔
445

446
    QFile::FileError error = fxi->loadXML(path);
10 ✔
447
    if (error == QFile::NoError)
10 ✔
448
    {
449
        /* A definition with no modes cannot be used to patch a fixture,
450
           so don't add it to the cache. Note that fixture editors load
451
           definitions directly, so they can still handle incomplete ones */
452
        if (fxi->modes().isEmpty())
2 ✔
453
        {
NEW
454
            qWarning() << Q_FUNC_INFO << "Fixture definition" << path
×
NEW
455
                       << "has no modes. Skipping.";
×
NEW
456
            delete fxi;
×
NEW
457
            return false;
×
458
        }
459

460
        fxi->setIsUser(isUser);
2 ✔
461
        fxi->setDefinitionSourceFile(path);
2 ✔
462
        fxi->setLoaded(true);
2 ✔
463

464
        /* Delete the def if it's a duplicate. */
465
        if (addFixtureDef(fxi) == false)
2 ✔
466
            delete fxi;
2 ✔
467
        fxi = NULL;
2 ✔
468
    }
469
    else
470
    {
471
        qWarning() << Q_FUNC_INFO << "Fixture definition loading from"
8 ✔
472
                   << path << "failed:" << QLCFile::errorString(error);
8 ✔
473
        delete fxi;
8 ✔
474
        fxi = NULL;
8 ✔
475
        return false;
8 ✔
476
    }
477
    return true;
2 ✔
478
}
479

480
bool QLCFixtureDefCache::loadD4(const QString& path)
1 ✔
481
{
482
    QLCFixtureDef *fxi = new QLCFixtureDef();
1 ✔
483
    AvolitesD4Parser parser;
1 ✔
484
    if (parser.loadXML(path, fxi) == false)
1 ✔
485
    {
486
        qWarning() << Q_FUNC_INFO << "Unable to load D4 fixture from" << path
1 ✔
487
                   << ":" << parser.lastError();
1 ✔
488
        delete fxi;
1 ✔
489
        return false;
1 ✔
490
    }
491

492
    // a D4 personality is always a user-made fixture
493
    fxi->setIsUser(true);
×
494
    fxi->setDefinitionSourceFile(path);
×
495
    fxi->setLoaded(true);
×
496

497
    /* Delete the def if it's a duplicate. */
498
    if (addFixtureDef(fxi) == false)
×
499
    {
500
        qDebug() << Q_FUNC_INFO << "Deleting duplicate" << path;
×
501
        delete fxi;
×
502
    }
503
    fxi = NULL;
×
504

505
    return true;
×
506
}
1 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc