1 /*
2    SPDX-FileCopyrightText: 2015 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
3 
4    SPDX-License-Identifier: LGPL-3.0-or-later
5  */
6 
7 #include "upnpcontentdirectorymodel.h"
8 
9 #include "musiclistenersmanager.h"
10 
11 #include "upnpLogging.h"
12 
13 #include "didlparser.h"
14 #include "upnpcontrolcontentdirectory.h"
15 #include "upnpdiscoverallmusic.h"
16 
17 #include <UpnpDeviceDescription>
18 #include <UpnpServiceDescription>
19 #include <UpnpActionDescription>
20 
21 #include <QDomDocument>
22 #include <QDomElement>
23 #include <QDomNode>
24 
25 #include <QHash>
26 #include <QString>
27 #include <QList>
28 #include <QLoggingCategory>
29 #include <QPointer>
30 #include <QUrl>
31 
32 class UpnpContentDirectoryModelPrivate
33 {
34 public:
35 
36     UpnpControlContentDirectory *mContentDirectory;
37 
38     DidlParser mDidlParser;
39 
40     QString mParentId = QStringLiteral("0");
41 
42     QString mBrowseFlag = QStringLiteral("BrowseDirectChildren");
43 
44     QString mFilter = QStringLiteral("*");
45 
46     QString mSortCriteria;
47 
48     quintptr mLastInternalId;
49 
50     QHash<QString, quintptr> mUpnpIds;
51 
52     QHash<quintptr, QVector<quintptr> > mChilds;
53 
54     QHash<quintptr, DataTypes::UpnpTrackDataType> mAllTrackData;
55 
56     int mCurrentUpdateId;
57 
58     bool mUseLocalIcons = false;
59 
60     bool mIsBusy = false;
61 
62 };
63 
UpnpContentDirectoryModel(QObject * parent)64 UpnpContentDirectoryModel::UpnpContentDirectoryModel(QObject *parent)
65     : QAbstractItemModel(parent), d(new UpnpContentDirectoryModelPrivate)
66 {
67     d->mContentDirectory = nullptr;
68 
69     connect(&d->mDidlParser, &DidlParser::isDataValidChanged, this, &UpnpContentDirectoryModel::contentChanged);
70 }
71 
72 UpnpContentDirectoryModel::~UpnpContentDirectoryModel()
73 = default;
74 
rowCount(const QModelIndex & parent) const75 int UpnpContentDirectoryModel::rowCount(const QModelIndex &parent) const
76 {
77     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::rowCount" << parent;
78 
79     int result = 0;
80 
81     auto currentInternalId = parent.internalId();
82 
83     if (!parent.isValid()) {
84         currentInternalId = d->mUpnpIds[parentId()];
85     }
86 
87     if (!d->mChilds.contains(currentInternalId)) {
88         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::rowCount" << parent << currentInternalId << "unknown child" << result;
89 
90         if (d->mAllTrackData.contains(currentInternalId)) {
91             result = d->mAllTrackData[currentInternalId][DataTypes::ChildCountRole].toInt();
92 
93             qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::rowCount" << parent << currentInternalId << result;
94         }
95 
96         return result;
97     }
98 
99     result = d->mChilds[currentInternalId].size();
100 
101     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::rowCount" << parent << currentInternalId << result;
102 
103     return result;
104 }
105 
roleNames() const106 QHash<int, QByteArray> UpnpContentDirectoryModel::roleNames() const
107 {
108     auto roles = QAbstractItemModel::roleNames();
109 
110     roles[static_cast<int>(DataTypes::TitleRole)] = "title";
111     roles[static_cast<int>(DataTypes::SecondaryTextRole)] = "secondaryText";
112     roles[static_cast<int>(DataTypes::ImageUrlRole)] = "imageUrl";
113     roles[static_cast<int>(DataTypes::DatabaseIdRole)] = "databaseId";
114     roles[static_cast<int>(DataTypes::ElementTypeRole)] = "dataType";
115     roles[static_cast<int>(DataTypes::ResourceRole)] = "url";
116 
117     roles[static_cast<int>(DataTypes::ArtistRole)] = "artist";
118     roles[static_cast<int>(DataTypes::AllArtistsRole)] = "allArtists";
119     roles[static_cast<int>(DataTypes::HighestTrackRating)] = "highestTrackRating";
120     roles[static_cast<int>(DataTypes::GenreRole)] = "genre";
121 
122     roles[static_cast<int>(DataTypes::AlbumRole)] = "album";
123     roles[static_cast<int>(DataTypes::AlbumArtistRole)] = "albumArtist";
124     roles[static_cast<int>(DataTypes::DurationRole)] = "duration";
125     roles[static_cast<int>(DataTypes::TrackNumberRole)] = "trackNumber";
126     roles[static_cast<int>(DataTypes::DiscNumberRole)] = "discNumber";
127     roles[static_cast<int>(DataTypes::RatingRole)] = "rating";
128     roles[static_cast<int>(DataTypes::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum";
129     roles[static_cast<int>(DataTypes::FullDataRole)] = "fullData";
130     roles[static_cast<int>(DataTypes::HasModelChildrenRole)] = "hasModelChildren";
131 
132     return roles;
133 }
134 
flags(const QModelIndex & index) const135 Qt::ItemFlags UpnpContentDirectoryModel::flags(const QModelIndex &index) const
136 {
137     if (!index.isValid()) {
138         return Qt::NoItemFlags;
139     }
140 
141     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
142 }
143 
data(const QModelIndex & index,int role) const144 QVariant UpnpContentDirectoryModel::data(const QModelIndex &index, int role) const
145 {
146     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::data" << index << role;
147 
148     QVariant result;
149 
150     if (!index.isValid()) {
151         return result;
152     }
153 
154     if (index.column() != 0) {
155         return result;
156     }
157 
158     if (index.row() < 0) {
159         return result;
160     }
161 
162     if (!d->mAllTrackData.contains(index.internalId())) {
163         return result;
164     }
165 
166     auto convertedRole = static_cast<DataTypes::ColumnsRoles>(role);
167 
168     switch(role)
169     {
170     case Qt::DisplayRole:
171         result = d->mAllTrackData[index.internalId()][DataTypes::TitleRole];
172         break;
173     case DataTypes::ElementTypeRole:
174     case DataTypes::IdRole:
175     case DataTypes::ParentIdRole:
176     case DataTypes::TitleRole:
177     case DataTypes::DurationRole:
178     case DataTypes::ArtistRole:
179     case DataTypes::AlbumRole:
180         result = d->mAllTrackData[index.internalId()][convertedRole];
181         break;
182     case DataTypes::RatingRole:
183         result = 0;
184         break;
185     case DataTypes::ResourceRole:
186         result = QUrl{};
187         break;
188     case DataTypes::ImageUrlRole:
189         switch (d->mAllTrackData[index.internalId()][DataTypes::ElementTypeRole].value<ElisaUtils::PlayListEntryType>())
190         {
191         case ElisaUtils::Album:
192             if (!d->mAllTrackData[index.internalId()][DataTypes::ImageUrlRole].toString().isEmpty()) {
193                 result = d->mAllTrackData[index.internalId()][DataTypes::ImageUrlRole].toUrl();
194             } else {
195                 if (d->mUseLocalIcons) {
196                     result = QUrl(QStringLiteral("qrc:/media-optical-audio.svg"));
197                 } else {
198                     result = QUrl(QStringLiteral("image://icon/media-optical-audio"));
199                 }
200             }
201             break;
202         case ElisaUtils::Container:
203         case ElisaUtils::UpnpMediaServer:
204             if (!d->mAllTrackData[index.internalId()][DataTypes::ImageUrlRole].toString().isEmpty()) {
205                 result = d->mAllTrackData[index.internalId()][DataTypes::ImageUrlRole].toUrl();
206             } else {
207                 if (d->mUseLocalIcons) {
208                     return QUrl(QStringLiteral("qrc:/folder.svg"));
209                 } else {
210                     return QUrl(QStringLiteral("image://icon/folder"));
211                 }
212             }
213             break;
214         case ElisaUtils::Track:
215             result = d->mAllTrackData[index.internalId()][DataTypes::ImageUrlRole];
216             break;
217         case ElisaUtils::Artist:
218         case ElisaUtils::Composer:
219         case ElisaUtils::FileName:
220         case ElisaUtils::Genre:
221         case ElisaUtils::Lyricist:
222         case ElisaUtils::Radio:
223         case ElisaUtils::Unknown:
224             break;
225         }
226         break;
227     case DataTypes::HasModelChildrenRole:
228         result = rowCount(index);
229         break;
230     case DataTypes::FullDataRole:
231         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::data" << d->mAllTrackData[index.internalId()];
232         result = QVariant::fromValue(static_cast<DataTypes::MusicDataType>(d->mAllTrackData[index.internalId()]));
233         break;
234     }
235 
236     return result;
237 }
238 
index(int row,int column,const QModelIndex & parent) const239 QModelIndex UpnpContentDirectoryModel::index(int row, int column, const QModelIndex &parent) const
240 {
241     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::index" << row << column << parent;
242 
243     QModelIndex result;
244 
245     auto currentInternalId = parent.internalId();
246 
247     if (!parent.isValid()) {
248         currentInternalId = d->mUpnpIds[parentId()];
249     }
250 
251     if (!d->mChilds.contains(currentInternalId)) {
252         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::index" << row << column << parent << result;
253 
254         return result;
255     }
256 
257     if (row < 0 || row >= d->mChilds[currentInternalId].size()) {
258         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::index" << row << column << parent << result;
259 
260         return result;
261     }
262 
263     if (column != 0) {
264         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::index" << row << column << parent << result;
265 
266         return result;
267     }
268 
269     result = createIndex(row, column, d->mChilds[currentInternalId][row]);
270 
271     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::index" << row << column << parent << result;
272 
273     return result;
274 }
275 
parent(const QModelIndex & child) const276 QModelIndex UpnpContentDirectoryModel::parent(const QModelIndex &child) const
277 {
278     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::parent" << child;
279 
280     QModelIndex result;
281 
282     // child is valid
283     if (!child.isValid()) {
284         return result;
285     }
286 
287     // data knows child internal id
288     if (!d->mAllTrackData.contains(child.internalId())) {
289         return result;
290     }
291 
292     const auto &childData = d->mAllTrackData[child.internalId()];
293 
294     // child data has upnp id of parent
295     if (!childData.contains(DataTypes::ParentIdRole)) {
296         return result;
297     }
298 
299     const auto &parentStringId = childData[DataTypes::ParentIdRole].toString();
300 
301     // upnp ids has the internal id of parent
302     if (!d->mUpnpIds.contains(parentStringId)) {
303         return result;
304     }
305 
306     // special case if we are already at top of model
307     if (parentStringId == QLatin1Char('0')) {
308         return result;
309     }
310 
311     auto parentInternalId = d->mUpnpIds[parentStringId];
312 
313     // data knows parent internal id
314     if (!d->mAllTrackData.contains(parentInternalId)) {
315         return result;
316     }
317 
318     const auto &parentData = d->mAllTrackData[parentInternalId];
319 
320     // parent data has upnp id of parent
321     if (!parentData.contains(DataTypes::ParentIdRole)) {
322         return result;
323     }
324 
325     const auto &grandParentStringId = parentData[DataTypes::ParentIdRole].toString();
326 
327     // upnp ids has the internal id of grand parent
328     if (!d->mUpnpIds.contains(grandParentStringId)) {
329         return result;
330     }
331 
332     auto grandParentInternalId = d->mUpnpIds[grandParentStringId];
333 
334     // childs of grand parent are known
335     if (!d->mChilds.contains(grandParentInternalId)) {
336         return result;
337     }
338 
339     const auto &allUncles = d->mChilds[grandParentInternalId];
340 
341     // look for my parent
342     for (int i = 0; i < allUncles.size(); ++i) {
343         if (allUncles[i] == parentInternalId) {
344             result = createIndex(i, 0, parentInternalId);
345             break;
346         }
347     }
348 
349     return result;
350 }
351 
columnCount(const QModelIndex & parent) const352 int UpnpContentDirectoryModel::columnCount(const QModelIndex &parent) const
353 {
354     Q_UNUSED(parent)
355 
356     return 1;
357 }
358 
canFetchMore(const QModelIndex & parent) const359 bool UpnpContentDirectoryModel::canFetchMore(const QModelIndex &parent) const
360 {
361     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::canFetchMore" << parent;
362 
363     bool result = false;
364 
365     auto parentInternalId = parent.internalId();
366 
367     if (!parent.isValid()) {
368         parentInternalId = d->mUpnpIds[parentId()];
369     }
370 
371     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::canFetchMore" << parent
372                                << parentInternalId << !d->mChilds.contains(parentInternalId)
373                                <<  d->mChilds[parentInternalId].isEmpty();
374 
375     result = !d->mChilds.contains(parentInternalId) ||  d->mChilds[parentInternalId].isEmpty();
376 
377     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::canFetchMore" << parent << result;
378 
379     return result;
380 }
381 
fetchMore(const QModelIndex & parent)382 void UpnpContentDirectoryModel::fetchMore(const QModelIndex &parent)
383 {
384     if (d->mIsBusy) {
385         return;
386     }
387 
388     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent;
389 
390     d->mIsBusy = true;
391     Q_EMIT isBusyChanged();
392 
393     if (!d->mContentDirectory) {
394         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent << "no content directory";
395 
396         d->mIsBusy = false;
397         Q_EMIT isBusyChanged();
398 
399         return;
400     }
401 
402     auto parentInternalId = parent.internalId();
403 
404     if (!parent.isValid()) {
405         parentInternalId = d->mUpnpIds[parentId()];
406     }
407 
408     if (!d->mAllTrackData.contains(parentInternalId)) {
409         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent << "no parent internal id";
410 
411         d->mIsBusy = false;
412         Q_EMIT isBusyChanged();
413 
414         return;
415     }
416 
417     if (!d->mAllTrackData[parentInternalId].contains(DataTypes::IdRole)) {
418         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent << "no id role";
419 
420         d->mIsBusy = false;
421         Q_EMIT isBusyChanged();
422 
423         return;
424     }
425 
426     if (parentId().isEmpty()) {
427         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent << d->mAllTrackData[parentInternalId][DataTypes::IdRole].toString();
428 
429         d->mDidlParser.setParentId(d->mAllTrackData[parentInternalId][DataTypes::IdRole].toString());
430     } else {
431         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::fetchMore" << parent << parentId();
432 
433         d->mDidlParser.setParentId(parentId());
434     }
435     d->mDidlParser.browse();
436 }
437 
parentId() const438 const QString &UpnpContentDirectoryModel::parentId() const
439 {
440     return d->mParentId;
441 }
442 
browseFlag() const443 const QString &UpnpContentDirectoryModel::browseFlag() const
444 {
445     return d->mBrowseFlag;
446 }
447 
setBrowseFlag(const QString & flag)448 void UpnpContentDirectoryModel::setBrowseFlag(const QString &flag)
449 {
450     d->mBrowseFlag = flag;
451     Q_EMIT browseFlagChanged();
452 }
453 
filter() const454 const QString &UpnpContentDirectoryModel::filter() const
455 {
456     return d->mFilter;
457 }
458 
setFilter(const QString & flag)459 void UpnpContentDirectoryModel::setFilter(const QString &flag)
460 {
461     d->mFilter = flag;
462     Q_EMIT filterChanged();
463 }
464 
sortCriteria() const465 const QString &UpnpContentDirectoryModel::sortCriteria() const
466 {
467     return d->mSortCriteria;
468 }
469 
setSortCriteria(const QString & criteria)470 void UpnpContentDirectoryModel::setSortCriteria(const QString &criteria)
471 {
472     d->mSortCriteria = criteria;
473     Q_EMIT sortCriteriaChanged();
474 }
475 
contentDirectory() const476 UpnpControlContentDirectory *UpnpContentDirectoryModel::contentDirectory() const
477 {
478     return d->mContentDirectory;
479 }
480 
setContentDirectory(UpnpControlContentDirectory * directory)481 void UpnpContentDirectoryModel::setContentDirectory(UpnpControlContentDirectory *directory)
482 {
483     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::setContentDirectory" << directory;
484     if (directory) {
485         beginResetModel();
486     }
487     if (d->mContentDirectory) {
488     }
489 
490     d->mContentDirectory = directory;
491 
492     if (!d->mContentDirectory) {
493         Q_EMIT contentDirectoryChanged();
494         return;
495     }
496 
497     d->mDidlParser.setContentDirectory(d->mContentDirectory);
498     d->mDidlParser.setBrowseFlag(browseFlag());
499     d->mDidlParser.setFilter(filter());
500     d->mDidlParser.setSortCriteria(sortCriteria());
501     d->mDidlParser.setParentId(parentId());
502 
503     endResetModel();
504 
505     Q_EMIT contentDirectoryChanged();
506 }
507 
useLocalIcons() const508 bool UpnpContentDirectoryModel::useLocalIcons() const
509 {
510     return d->mUseLocalIcons;
511 }
512 
setUseLocalIcons(bool value)513 void UpnpContentDirectoryModel::setUseLocalIcons(bool value)
514 {
515     d->mUseLocalIcons = value;
516     Q_EMIT useLocalIconsChanged();
517 }
518 
isBusy() const519 bool UpnpContentDirectoryModel::isBusy() const
520 {
521     return d->mIsBusy;
522 }
523 
initializeByData(MusicListenersManager * manager,DatabaseInterface * database,ElisaUtils::PlayListEntryType modelType,ElisaUtils::FilterType filter,const DataTypes::DataType & dataFilter)524 void UpnpContentDirectoryModel::initializeByData(MusicListenersManager *manager, DatabaseInterface *database,
525                                                  ElisaUtils::PlayListEntryType modelType, ElisaUtils::FilterType filter,
526                                                  const DataTypes::DataType &dataFilter)
527 {
528     Q_UNUSED(database)
529     Q_UNUSED(modelType)
530     Q_UNUSED(filter)
531 
532     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::initializeByData" << modelType << filter << dataFilter << dataFilter[DataTypes::UUIDRole].toString() << dataFilter[DataTypes::IdRole].toString();
533 
534     if (manager && manager->ssdpEngine() && manager->upnpServiceDiscovery()) {
535         auto *newContentDirectory = new UpnpControlContentDirectory;
536 
537         const auto &deviceDescription = manager->upnpServiceDiscovery()->deviceDescriptionByUdn(dataFilter[DataTypes::UUIDRole].toString());
538         newContentDirectory->setDescription(deviceDescription.serviceById(QStringLiteral("urn:upnp-org:serviceId:ContentDirectory")));
539         setParentId(dataFilter[DataTypes::IdRole].toString());
540         setContentDirectory(newContentDirectory);
541         d->mDidlParser.setDeviceUUID(dataFilter[DataTypes::UUIDRole].toString());
542     }
543 }
544 
setParentId(QString parentId)545 void UpnpContentDirectoryModel::setParentId(QString parentId)
546 {
547     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::setParentId" << parentId;
548 
549     if (d->mParentId == parentId) {
550         return;
551     }
552 
553     if (parentId.isEmpty()) {
554         parentId = QStringLiteral("0");
555     }
556 
557     d->mParentId = std::move(parentId);
558     d->mDidlParser.setParentId(d->mParentId);
559 
560     Q_EMIT parentIdChanged();
561 
562     beginResetModel();
563     d->mLastInternalId = 1;
564 
565     d->mUpnpIds[d->mParentId] = d->mLastInternalId;
566 
567     d->mChilds[d->mLastInternalId] = QVector<quintptr>();
568 
569     d->mAllTrackData[d->mLastInternalId] = {{DataTypes::IdRole, d->mParentId},
570                                             {DataTypes::ElementTypeRole, QVariant::fromValue(ElisaUtils::Container)},
571                                             {DataTypes::TitleRole, QStringLiteral("Root")},};
572 
573     ++d->mLastInternalId;
574 
575     d->mCurrentUpdateId = -1;
576     endResetModel();
577 }
578 
indexFromInternalId(quintptr internalId) const579 QModelIndex UpnpContentDirectoryModel::indexFromInternalId(quintptr internalId) const
580 {
581     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId;
582 
583     QModelIndex result;
584 
585     if (internalId == 1) {
586         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << result;
587 
588         return result;
589     }
590 
591     // data knows child internal id
592     if (!d->mAllTrackData.contains(internalId)) {
593         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << "unknown children id" << result;
594 
595         return result;
596     }
597 
598     const auto &childData = d->mAllTrackData[internalId];
599 
600     // child data has upnp id of parent
601     if (!childData.contains(DataTypes::ParentIdRole)) {
602         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << "unknown parent" << result;
603 
604         return result;
605     }
606 
607     const auto &parentStringId = childData[DataTypes::ParentIdRole].toString();
608 
609     // upnp ids has the internal id of parent
610     if (!d->mUpnpIds.contains(parentStringId)) {
611         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << "unknown parent from ids list" << result;
612 
613         return result;
614     }
615 
616     auto parentInternalId = d->mUpnpIds[parentStringId];
617 
618     // childs of parent are known
619     if (!d->mChilds.contains(parentInternalId)) {
620         qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << "unknown parent id" << result;
621 
622         return result;
623     }
624 
625     const auto &allUncles = d->mChilds[parentInternalId];
626 
627     // look for my parent
628     for (int i = 0; i < allUncles.size(); ++i) {
629         if (allUncles[i] == internalId) {
630             result = createIndex(i, 0, internalId);
631             break;
632         }
633     }
634 
635     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::indexFromInternalId" << internalId << result;
636 
637     return result;
638 }
639 
contentChanged(const QString & parentId)640 void UpnpContentDirectoryModel::contentChanged(const QString &parentId)
641 {
642     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::contentChanged" << parentId;
643 
644     auto parentInternalId = d->mUpnpIds[parentId];
645 
646     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::contentChanged" << parentId
647                                << parentInternalId
648                                << indexFromInternalId(parentInternalId)
649                                << 0 << d->mDidlParser.newMusicTracks().size() - 1;
650     beginInsertRows(indexFromInternalId(parentInternalId), 0, d->mDidlParser.newMusicTracks().size() - 1);
651 
652     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::contentChanged" << parentId << parentInternalId;
653 
654     for(const auto &oneUpnpTrack : std::as_const(d->mDidlParser.newMusicTracks())) {
655         d->mAllTrackData[d->mLastInternalId] = oneUpnpTrack;
656         d->mUpnpIds[oneUpnpTrack[DataTypes::IdRole].toString()] = d->mLastInternalId;
657         d->mChilds[parentInternalId].push_back(d->mLastInternalId);
658         ++d->mLastInternalId;
659     }
660 
661     qCDebug(orgKdeElisaUpnp()) << "UpnpContentDirectoryModel::contentChanged" << parentId << d->mChilds[parentInternalId].size();
662 
663     endInsertRows();
664 
665     d->mIsBusy = false;
666     Q_EMIT isBusyChanged();
667 }
668 
669 #include "moc_upnpcontentdirectorymodel.cpp"
670