File indexing completed on 2024-04-28 04:49:04
0001 /* 0002 SPDX-FileCopyrightText: 2015 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 0003 0004 SPDX-License-Identifier: LGPL-3.0-or-later 0005 */ 0006 0007 #include "didlparser.h" 0008 0009 #include "upnpcontrolcontentdirectory.h" 0010 #include "upnpcontrolabstractservicereply.h" 0011 #include "upnpservicedescription.h" 0012 #include "upnpdevicedescription.h" 0013 #include "elisautils.h" 0014 0015 #include "upnpLogging.h" 0016 0017 #include <QVector> 0018 #include <QString> 0019 0020 #include <QDomDocument> 0021 #include <QDomNode> 0022 0023 class DidlParserPrivate 0024 { 0025 public: 0026 0027 QString mBrowseFlag = QStringLiteral("*"); 0028 0029 QString mFilter = QStringLiteral("*"); 0030 0031 QString mSortCriteria; 0032 0033 QString mSearchCriteria; 0034 0035 QString mParentId; 0036 0037 QString mDeviceUUID; 0038 0039 UpnpControlContentDirectory *mContentDirectory = nullptr; 0040 0041 QVector<QString> mNewMusicTrackIds; 0042 0043 QHash<QString, DataTypes::UpnpTrackDataType> mNewMusicTracks; 0044 0045 QHash<QString, QVector<DataTypes::UpnpTrackDataType>> mNewTracksByAlbums; 0046 0047 QHash<QString, QUrl> mCovers; 0048 0049 bool mIsDataValid = false; 0050 0051 }; 0052 0053 DidlParser::DidlParser(QObject *parent) : QObject(parent), d(new DidlParserPrivate) 0054 { 0055 } 0056 0057 DidlParser::~DidlParser() 0058 = default; 0059 0060 const QString &DidlParser::browseFlag() const 0061 { 0062 return d->mBrowseFlag; 0063 } 0064 0065 const QString &DidlParser::filter() const 0066 { 0067 return d->mFilter; 0068 } 0069 0070 const QString &DidlParser::sortCriteria() const 0071 { 0072 return d->mSortCriteria; 0073 } 0074 0075 const QString &DidlParser::searchCriteria() const 0076 { 0077 return d->mSearchCriteria; 0078 } 0079 0080 UpnpControlContentDirectory *DidlParser::contentDirectory() const 0081 { 0082 return d->mContentDirectory; 0083 } 0084 0085 bool DidlParser::isDataValid() const 0086 { 0087 return d->mIsDataValid; 0088 } 0089 0090 void DidlParser::setBrowseFlag(QString flag) 0091 { 0092 if (d->mBrowseFlag == flag) { 0093 return; 0094 } 0095 0096 d->mBrowseFlag = std::move(flag); 0097 Q_EMIT browseFlagChanged(); 0098 } 0099 0100 void DidlParser::setFilter(QString flag) 0101 { 0102 if (d->mFilter == flag) { 0103 return; 0104 } 0105 0106 d->mFilter = std::move(flag); 0107 Q_EMIT filterChanged(); 0108 } 0109 0110 void DidlParser::setSortCriteria(QString criteria) 0111 { 0112 if (d->mSortCriteria == criteria) { 0113 return; 0114 } 0115 0116 d->mSortCriteria = std::move(criteria); 0117 Q_EMIT sortCriteriaChanged(); 0118 } 0119 0120 void DidlParser::setSearchCriteria(QString criteria) 0121 { 0122 if (d->mSearchCriteria == criteria) { 0123 return; 0124 } 0125 0126 d->mSearchCriteria = std::move(criteria); 0127 Q_EMIT searchCriteriaChanged(); 0128 } 0129 0130 void DidlParser::setContentDirectory(UpnpControlContentDirectory *directory) 0131 { 0132 d->mContentDirectory = directory; 0133 0134 if (!d->mContentDirectory) { 0135 Q_EMIT contentDirectoryChanged(); 0136 return; 0137 } 0138 0139 Q_EMIT contentDirectoryChanged(); 0140 } 0141 0142 void DidlParser::setParentId(QString parentId) 0143 { 0144 if (d->mParentId == parentId) { 0145 return; 0146 } 0147 0148 d->mParentId = std::move(parentId); 0149 Q_EMIT parentIdChanged(); 0150 } 0151 0152 void DidlParser::setDeviceUUID(QString deviceUUID) 0153 { 0154 if (d->mDeviceUUID == deviceUUID) { 0155 return; 0156 } 0157 0158 d->mDeviceUUID = std::move(deviceUUID); 0159 Q_EMIT deviceUUIDChanged(); 0160 } 0161 0162 void DidlParser::systemUpdateIDChanged() 0163 { 0164 search(); 0165 } 0166 0167 void DidlParser::browse(int startIndex, int maximumNmberOfResults) 0168 { 0169 qCDebug(orgKdeElisaUpnp()) << "DidlParser::browse" << d->mParentId << d->mBrowseFlag << d->mFilter << startIndex << maximumNmberOfResults << d->mSortCriteria; 0170 0171 auto upnpAnswer = d->mContentDirectory->browse(d->mParentId, d->mBrowseFlag, d->mFilter, startIndex, maximumNmberOfResults, d->mSortCriteria); 0172 0173 if (startIndex == 0) { 0174 d->mNewMusicTracks.clear(); 0175 d->mNewMusicTrackIds.clear(); 0176 d->mCovers.clear(); 0177 } 0178 0179 connect(upnpAnswer, &UpnpControlAbstractServiceReply::finished, this, &DidlParser::browseFinished); 0180 } 0181 0182 void DidlParser::search(int startIndex, int maximumNumberOfResults) 0183 { 0184 if (!d->mContentDirectory) { 0185 return; 0186 } 0187 0188 if (startIndex == 0) { 0189 d->mNewMusicTracks.clear(); 0190 d->mNewMusicTrackIds.clear(); 0191 d->mCovers.clear(); 0192 } 0193 0194 auto upnpAnswer = d->mContentDirectory->search(d->mParentId, d->mSearchCriteria, d->mFilter, startIndex, maximumNumberOfResults, d->mSortCriteria); 0195 0196 connect(upnpAnswer, &UpnpControlAbstractServiceReply::finished, this, &DidlParser::searchFinished); 0197 } 0198 0199 QString DidlParser::parentId() const 0200 { 0201 return d->mParentId; 0202 } 0203 0204 const QString &DidlParser::deviceUUID() const 0205 { 0206 return d->mDeviceUUID; 0207 } 0208 0209 const QVector<QString> &DidlParser::newMusicTrackIds() const 0210 { 0211 return d->mNewMusicTrackIds; 0212 } 0213 0214 const QHash<QString, DataTypes::UpnpTrackDataType> &DidlParser::newMusicTracks() const 0215 { 0216 return d->mNewMusicTracks; 0217 } 0218 0219 const QHash<QString, QUrl> &DidlParser::covers() const 0220 { 0221 return d->mCovers; 0222 } 0223 0224 void DidlParser::browseFinished(UpnpControlAbstractServiceReply *self) 0225 { 0226 qCDebug(orgKdeElisaUpnp()) << "DidlParser::browseFinished"; 0227 0228 const auto &resultData = self->result(); 0229 0230 bool success = self->success(); 0231 0232 if (!success) { 0233 qCDebug(orgKdeElisaUpnp()) << "DidlParser::browseFinished" << "error" << self->error(); 0234 0235 d->mIsDataValid = false; 0236 Q_EMIT isDataValidChanged(d->mParentId); 0237 0238 return; 0239 } 0240 0241 QString result = resultData[QStringLiteral("Result")].toString(); 0242 0243 bool intConvert; 0244 auto numberReturned = resultData[QStringLiteral("NumberReturned")].toInt(&intConvert); 0245 0246 if (!intConvert) { 0247 d->mIsDataValid = false; 0248 Q_EMIT isDataValidChanged(d->mParentId); 0249 0250 return; 0251 } 0252 0253 auto totalMatches = resultData[QStringLiteral("TotalMatches")].toInt(&intConvert); 0254 0255 if (!intConvert) { 0256 d->mIsDataValid = false; 0257 Q_EMIT isDataValidChanged(d->mParentId); 0258 0259 return; 0260 } 0261 0262 if (totalMatches > numberReturned) { 0263 browse(d->mNewMusicTracks.size() + numberReturned); 0264 } 0265 0266 QDomDocument browseDescription; 0267 browseDescription.setContent(result); 0268 0269 browseDescription.documentElement(); 0270 0271 auto containerList = browseDescription.elementsByTagName(QStringLiteral("container")); 0272 for (int containerIndex = 0; containerIndex < containerList.length(); ++containerIndex) { 0273 const QDomNode &containerNode(containerList.at(containerIndex)); 0274 if (!containerNode.isNull()) { 0275 decodeContainerNode(containerNode, d->mNewMusicTracks, d->mNewMusicTrackIds); 0276 } 0277 } 0278 0279 auto itemList = browseDescription.elementsByTagName(QStringLiteral("item")); 0280 for (int itemIndex = 0; itemIndex < itemList.length(); ++itemIndex) { 0281 const QDomNode &itemNode(itemList.at(itemIndex)); 0282 if (!itemNode.isNull()) { 0283 qCInfo(orgKdeElisaUpnp()) << "DidlParser::browseFinished" << "new track node" << itemNode.toDocument().toString(); 0284 0285 decodeAudioTrackNode(itemNode, d->mNewMusicTracks, d->mNewMusicTrackIds); 0286 } 0287 } 0288 0289 groupNewTracksByAlbums(); 0290 d->mIsDataValid = true; 0291 Q_EMIT isDataValidChanged(d->mParentId); 0292 } 0293 0294 void DidlParser::groupNewTracksByAlbums() 0295 { 0296 d->mNewTracksByAlbums.clear(); 0297 for(const auto &newTrack : std::as_const(d->mNewMusicTracks)) { 0298 d->mNewTracksByAlbums[newTrack.album()].push_back(newTrack); 0299 } 0300 } 0301 0302 void DidlParser::searchFinished(UpnpControlAbstractServiceReply *self) 0303 { 0304 qCDebug(orgKdeElisaUpnp()) << "DidlParser::searchFinished"; 0305 0306 const auto &resultData = self->result(); 0307 0308 bool success = self->success(); 0309 0310 if (!success) { 0311 qCDebug(orgKdeElisaUpnp()) << "DidlParser::searchFinished" << "error" << self->error(); 0312 0313 d->mIsDataValid = false; 0314 Q_EMIT isDataValidChanged(d->mParentId); 0315 0316 return; 0317 } 0318 0319 QString result = resultData[QStringLiteral("Result")].toString(); 0320 0321 qCDebug(orgKdeElisaUpnp()) << "DidlParser::searchFinished" << "result" << result; 0322 0323 bool intConvert; 0324 auto numberReturned = resultData[QStringLiteral("NumberReturned")].toInt(&intConvert); 0325 0326 qCDebug(orgKdeElisaUpnp()) << "DidlParser::searchFinished" << "NumberReturned" << numberReturned; 0327 0328 if (!intConvert) { 0329 d->mIsDataValid = false; 0330 Q_EMIT isDataValidChanged(d->mParentId); 0331 0332 return; 0333 } 0334 0335 auto totalMatches = resultData[QStringLiteral("TotalMatches")].toInt(&intConvert); 0336 0337 qCDebug(orgKdeElisaUpnp()) << "DidlParser::searchFinished" << "TotalMatches" << totalMatches; 0338 0339 if (!intConvert) { 0340 d->mIsDataValid = false; 0341 Q_EMIT isDataValidChanged(d->mParentId); 0342 0343 return; 0344 } 0345 0346 if (totalMatches > numberReturned) { 0347 search(d->mNewMusicTracks.size() + numberReturned, numberReturned); 0348 } 0349 0350 QDomDocument browseDescription; 0351 browseDescription.setContent(result); 0352 0353 browseDescription.documentElement(); 0354 0355 auto containerList = browseDescription.elementsByTagName(QStringLiteral("container")); 0356 for (int containerIndex = 0; containerIndex < containerList.length(); ++containerIndex) { 0357 const QDomNode &containerNode(containerList.at(containerIndex)); 0358 if (!containerNode.isNull()) { 0359 decodeContainerNode(containerNode, d->mNewMusicTracks, d->mNewMusicTrackIds); 0360 } 0361 } 0362 0363 auto itemList = browseDescription.elementsByTagName(QStringLiteral("item")); 0364 for (int itemIndex = 0; itemIndex < itemList.length(); ++itemIndex) { 0365 const QDomNode &itemNode(itemList.at(itemIndex)); 0366 if (!itemNode.isNull()) { 0367 decodeAudioTrackNode(itemNode, d->mNewMusicTracks, d->mNewMusicTrackIds); 0368 } 0369 } 0370 0371 groupNewTracksByAlbums(); 0372 d->mIsDataValid = true; 0373 Q_EMIT isDataValidChanged(d->mParentId); 0374 } 0375 0376 void DidlParser::decodeContainerNode(const QDomNode &containerNode, QHash<QString, DataTypes::UpnpTrackDataType> &newData, 0377 QVector<QString> &newDataIds) 0378 { 0379 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeContainerNode"; 0380 0381 auto allChilds = containerNode.childNodes(); 0382 0383 for(int i = 0; i < allChilds.count(); ++i) { 0384 const auto &oneChild = allChilds.at(i); 0385 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeContainerNode" << oneChild.nodeName(); 0386 } 0387 0388 auto parentID = containerNode.toElement().attribute(QStringLiteral("parentID")); 0389 const auto &id = containerNode.toElement().attribute(QStringLiteral("id")); 0390 0391 newDataIds.push_back(id); 0392 auto &childData = newData[id]; 0393 0394 childData[DataTypes::ColumnsRoles::ParentIdRole] = parentID; 0395 childData[DataTypes::ColumnsRoles::IdRole] = id; 0396 0397 const QString &childCount = containerNode.toElement().attribute(QStringLiteral("childCount")); 0398 childData[DataTypes::ColumnsRoles::ChildCountRole] = childCount.toInt(); 0399 0400 const QDomNode &titleNode = containerNode.firstChildElement(QStringLiteral("dc:title")); 0401 if (!titleNode.isNull()) { 0402 childData[DataTypes::ColumnsRoles::TitleRole] = titleNode.toElement().text(); 0403 } 0404 0405 const QDomNode &authorNode = containerNode.firstChildElement(QStringLiteral("upnp:artist")); 0406 if (!authorNode.isNull()) { 0407 childData[DataTypes::ColumnsRoles::ArtistRole] = authorNode.toElement().text(); 0408 } 0409 0410 const QDomNode &resourceNode = containerNode.firstChildElement(QStringLiteral("res")); 0411 if (!resourceNode.isNull()) { 0412 childData[DataTypes::ColumnsRoles::ResourceRole] = QUrl::fromUserInput(resourceNode.toElement().text()); 0413 } 0414 0415 const QDomNode &classNode = containerNode.firstChildElement(QStringLiteral("upnp:class")); 0416 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeContainerNode" << "upnp:class" << classNode.toElement().text(); 0417 // if (classNode.toElement().text().startsWith(QLatin1String("object.container.album.musicAlbum"))) { 0418 // childData[DataTypes::ElementTypeRole] = QVariant::fromValue(ElisaUtils::Album); 0419 // } else if (classNode.toElement().text().startsWith(QLatin1String("object.container.person.musicArtist"))) { 0420 // childData[DataTypes::ElementTypeRole] = QVariant::fromValue(ElisaUtils::Artist); 0421 // } else if (classNode.toElement().text().startsWith(QLatin1String("object.container"))) { 0422 childData[DataTypes::ElementTypeRole] = QVariant::fromValue(ElisaUtils::UpnpMediaServer); 0423 childData[DataTypes::UUIDRole] = d->mDeviceUUID; 0424 // } 0425 0426 const QDomNode &albumArtNode = containerNode.firstChildElement(QStringLiteral("upnp:albumArtURI")); 0427 if (!albumArtNode.isNull()) { 0428 childData[DataTypes::ColumnsRoles::ImageUrlRole] = QUrl::fromUserInput(albumArtNode.toElement().text()); 0429 } 0430 0431 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeContainerNode" << childData; 0432 } 0433 0434 void DidlParser::decodeAudioTrackNode(const QDomNode &itemNode, QHash<QString, DataTypes::UpnpTrackDataType> &newData, 0435 QVector<QString> &newDataIds) 0436 { 0437 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeAudioTrackNode"; 0438 0439 auto allChilds = itemNode.childNodes(); 0440 0441 for(int i = 0; i < allChilds.count(); ++i) { 0442 const auto &oneChild = allChilds.at(i); 0443 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeAudioTrackNode" << oneChild.nodeName(); 0444 } 0445 0446 const QString &parentID = itemNode.toElement().attribute(QStringLiteral("parentID")); 0447 const QString &id = itemNode.toElement().attribute(QStringLiteral("id")); 0448 0449 newDataIds.push_back(id); 0450 auto &childData = newData[id]; 0451 0452 childData[DataTypes::ElementTypeRole] = QVariant::fromValue(ElisaUtils::Track); 0453 childData[DataTypes::ColumnsRoles::ParentIdRole] = parentID; 0454 childData[DataTypes::ColumnsRoles::IdRole] = id; 0455 0456 const QDomNode &titleNode = itemNode.firstChildElement(QStringLiteral("dc:title")); 0457 if (!titleNode.isNull()) { 0458 childData[DataTypes::ColumnsRoles::TitleRole] = titleNode.toElement().text(); 0459 } 0460 0461 const QDomNode &authorNode = itemNode.firstChildElement(QStringLiteral("dc:creator")); 0462 if (!authorNode.isNull()) { 0463 childData[DataTypes::ColumnsRoles::ArtistRole] = authorNode.toElement().text(); 0464 } 0465 0466 const QDomNode &albumAuthorNode = itemNode.firstChildElement(QStringLiteral("upnp:artist")); 0467 if (!albumAuthorNode.isNull()) { 0468 childData[DataTypes::ColumnsRoles::AlbumArtistRole] = albumAuthorNode.toElement().text(); 0469 } 0470 0471 if (childData.albumArtist().isEmpty()) { 0472 childData[DataTypes::ColumnsRoles::AlbumArtistRole] = childData.artist(); 0473 } 0474 0475 if (childData.artist().isEmpty()) { 0476 childData[DataTypes::ColumnsRoles::ArtistRole] = childData.albumArtist(); 0477 } 0478 0479 const QDomNode &albumNode = itemNode.firstChildElement(QStringLiteral("upnp:album")); 0480 if (!albumNode.isNull()) { 0481 childData.setAlbum(albumNode.toElement().text()); 0482 } 0483 0484 const QDomNode &albumArtNode = itemNode.firstChildElement(QStringLiteral("upnp:albumArtURI")); 0485 if (!albumArtNode.isNull()) { 0486 childData[DataTypes::ColumnsRoles::ImageUrlRole] = QUrl::fromUserInput(albumArtNode.toElement().text()); 0487 } 0488 0489 const QDomNode &resourceNode = itemNode.firstChildElement(QStringLiteral("res")); 0490 if (!resourceNode.isNull()) { 0491 childData[DataTypes::ColumnsRoles::ResourceRole] = QUrl::fromUserInput(resourceNode.toElement().text()); 0492 if (resourceNode.attributes().contains(QStringLiteral("duration"))) { 0493 const QDomNode &durationNode = resourceNode.attributes().namedItem(QStringLiteral("duration")); 0494 QString durationValue = durationNode.nodeValue(); 0495 if (durationValue.startsWith(QLatin1String("0:"))) { 0496 durationValue.remove(0, 2); 0497 } 0498 if (durationValue.contains(uint('.'))) { 0499 durationValue = durationValue.split(QLatin1Char('.')).first(); 0500 } 0501 0502 childData[DataTypes::ColumnsRoles::DurationRole] = QTime::fromString(durationValue, QStringLiteral("mm:ss")); 0503 if (!childData.duration().isValid()) { 0504 childData[DataTypes::ColumnsRoles::DurationRole] = QTime::fromString(durationValue, QStringLiteral("hh:mm:ss")); 0505 if (!childData.duration().isValid()) { 0506 childData[DataTypes::ColumnsRoles::DurationRole] = QTime::fromString(durationValue, QStringLiteral("hh:mm:ss.z")); 0507 } 0508 } 0509 } 0510 0511 const QDomNode &trackNumberNode = itemNode.firstChildElement(QStringLiteral("upnp:originalTrackNumber")); 0512 if (!trackNumberNode.isNull()) { 0513 childData[DataTypes::ColumnsRoles::TrackNumberRole] = trackNumberNode.toElement().text().toInt(); 0514 } 0515 0516 if (resourceNode.attributes().contains(QStringLiteral("artist"))) { 0517 const QDomNode &artistNode = resourceNode.attributes().namedItem(QStringLiteral("artist")); 0518 childData[DataTypes::ColumnsRoles::ArtistRole] = artistNode.nodeValue(); 0519 } 0520 } 0521 0522 qCDebug(orgKdeElisaUpnp()) << "DidlParser::decodeAudioTrackNode" << childData; 0523 } 0524 0525 0526 #include "moc_didlparser.cpp" 0527