00001 #include <QCoreApplication>
00002 #include <QTextCodec>
00003
00004 #include "mythcorecontext.h"
00005 #include "mythlogging.h"
00006 #include "ssdp.h"
00007 #include "upnpscanner.h"
00008
00009 #define LOC QString("UPnPScan: ")
00010 #define ERR QString("UPnPScan error: ")
00011
00012 #define MAX_ATTEMPTS 5
00013 #define MAX_REQUESTS 1
00014
00015 QString MediaServerItem::NextUnbrowsed(void)
00016 {
00017
00018 if (!m_url.isEmpty())
00019 return QString();
00020
00021
00022 if (!m_scanned)
00023 return m_id;
00024
00025
00026 QMutableMapIterator<QString,MediaServerItem> it(m_children);
00027 while (it.hasNext())
00028 {
00029 it.next();
00030 QString result = it.value().NextUnbrowsed();
00031 if (!result.isEmpty())
00032 return result;
00033 }
00034
00035 return QString();
00036 }
00037
00038 MediaServerItem* MediaServerItem::Find(QString &id)
00039 {
00040 if (m_id == id)
00041 return this;
00042
00043 QMutableMapIterator<QString,MediaServerItem> it(m_children);
00044 while (it.hasNext())
00045 {
00046 it.next();
00047 MediaServerItem* result = it.value().Find(id);
00048 if (result)
00049 return result;
00050 }
00051
00052 return NULL;
00053 }
00054
00055 bool MediaServerItem::Add(MediaServerItem &item)
00056 {
00057 if (m_id == item.m_parentid)
00058 {
00059 m_children.insert(item.m_id, item);
00060 return true;
00061 }
00062 return false;
00063 }
00064
00065 void MediaServerItem::Reset(void)
00066 {
00067 m_children.clear();
00068 m_scanned = false;
00069 }
00070
00075 class MediaServer : public MediaServerItem
00076 {
00077 public:
00078 MediaServer()
00079 : MediaServerItem(QString("0"), QString(), QString(), QString()),
00080 m_URL(), m_connectionAttempts(0), m_controlURL(QUrl()),
00081 m_eventSubURL(QUrl()), m_eventSubPath(QString()),
00082 m_friendlyName(QString("Unknown")), m_subscribed(false),
00083 m_renewalTimerId(0), m_systemUpdateID(-1)
00084 {
00085 }
00086 MediaServer(QUrl URL)
00087 : MediaServerItem(QString("0"), QString(), QString(), QString()),
00088 m_URL(URL), m_connectionAttempts(0), m_controlURL(QUrl()),
00089 m_eventSubURL(QUrl()), m_eventSubPath(QString()),
00090 m_friendlyName(QString("Unknown")), m_subscribed(false),
00091 m_renewalTimerId(0), m_systemUpdateID(-1)
00092 {
00093 }
00094
00095 bool ResetContent(int new_id)
00096 {
00097 bool result = true;
00098 if (m_systemUpdateID != -1)
00099 {
00100 result = false;
00101 Reset();
00102 }
00103 m_systemUpdateID = new_id;
00104 return result;
00105 }
00106
00107 QUrl m_URL;
00108 int m_connectionAttempts;
00109 QUrl m_controlURL;
00110 QUrl m_eventSubURL;
00111 QString m_eventSubPath;
00112 QString m_friendlyName;
00113 bool m_subscribed;
00114 int m_renewalTimerId;
00115 int m_systemUpdateID;
00116 };
00117
00118 UPNPScanner* UPNPScanner::gUPNPScanner = NULL;
00119 bool UPNPScanner::gUPNPScannerEnabled = false;
00120 MThread* UPNPScanner::gUPNPScannerThread = NULL;
00121 QMutex* UPNPScanner::gUPNPScannerLock = new QMutex(QMutex::Recursive);
00122
00133 UPNPScanner::UPNPScanner(UPNPSubscription *sub)
00134 : QObject(), m_subscription(sub), m_lock(QMutex::Recursive),
00135 m_network(NULL), m_updateTimer(NULL), m_watchdogTimer(NULL),
00136 m_masterHost(QString()), m_masterPort(0), m_scanComplete(false),
00137 m_fullscan(false)
00138 {
00139 }
00140
00141 UPNPScanner::~UPNPScanner()
00142 {
00143 Stop();
00144 }
00145
00150 void UPNPScanner::Enable(bool enable, UPNPSubscription *sub)
00151 {
00152 QMutexLocker locker(gUPNPScannerLock);
00153 gUPNPScannerEnabled = enable;
00154 Instance(sub);
00155 }
00156
00162 UPNPScanner* UPNPScanner::Instance(UPNPSubscription *sub)
00163 {
00164 QMutexLocker locker(gUPNPScannerLock);
00165 if (!gUPNPScannerEnabled)
00166 {
00167 if (gUPNPScannerThread)
00168 {
00169 gUPNPScannerThread->quit();
00170 gUPNPScannerThread->wait();
00171 }
00172 delete gUPNPScannerThread;
00173 gUPNPScannerThread = NULL;
00174 delete gUPNPScanner;
00175 gUPNPScanner = NULL;
00176 return NULL;
00177 }
00178
00179 if (!gUPNPScannerThread)
00180 gUPNPScannerThread = new MThread("UPnPScanner");
00181 if (!gUPNPScanner)
00182 gUPNPScanner = new UPNPScanner(sub);
00183
00184 if (!gUPNPScannerThread->isRunning())
00185 {
00186 gUPNPScanner->moveToThread(gUPNPScannerThread->qthread());
00187 QObject::connect(
00188 gUPNPScannerThread->qthread(), SIGNAL(started()),
00189 gUPNPScanner, SLOT(Start()));
00190 gUPNPScannerThread->start(QThread::LowestPriority);
00191 }
00192
00193 return gUPNPScanner;
00194 }
00200 void UPNPScanner::StartFullScan(void)
00201 {
00202 m_fullscan = true;
00203 MythEvent *me = new MythEvent(QString("UPNP_STARTSCAN"));
00204 qApp->postEvent(this, me);
00205 }
00206
00213 void UPNPScanner::GetInitialMetadata(VideoMetadataListManager::metadata_list* list,
00214 meta_dir_node *node)
00215 {
00216
00217 QMap<QString,QString> servers = ServerList();
00218 if (servers.isEmpty())
00219 return;
00220
00221
00222 LOG(VB_GENERAL, LOG_INFO, QString("Adding MediaServer metadata."));
00223
00224 smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
00225 mediaservers->setPathRoot();
00226
00227 m_lock.lock();
00228 QMutableHashIterator<QString,MediaServer*> it(m_servers);
00229 while (it.hasNext())
00230 {
00231 it.next();
00232 if (!it.value()->m_subscribed)
00233 continue;
00234
00235 QString usn = it.key();
00236 GetServerContent(usn, it.value(), list, mediaservers.get());
00237 }
00238 m_lock.unlock();
00239 }
00240
00246 void UPNPScanner::GetMetadata(VideoMetadataListManager::metadata_list* list,
00247 meta_dir_node *node)
00248 {
00249
00250 QMap<QString,QString> servers = ServerList();
00251 if (servers.isEmpty())
00252 return;
00253
00254
00255 StartFullScan();
00256
00257
00258 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for scan to complete.");
00259
00260 int count = 0;
00261 while (!m_scanComplete && (count++ < 300))
00262 usleep(100000);
00263
00264
00265 if (!m_scanComplete)
00266 LOG(VB_GENERAL, LOG_ERR, LOC + "MediaServer scan is incomplete.");
00267 else
00268 LOG(VB_GENERAL, LOG_INFO, LOC + "MediaServer scanning finished.");
00269
00270
00271 smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
00272 mediaservers->setPathRoot();
00273
00274 m_lock.lock();
00275 QMutableHashIterator<QString,MediaServer*> it(m_servers);
00276 while (it.hasNext())
00277 {
00278 it.next();
00279 if (!it.value()->m_subscribed)
00280 continue;
00281
00282 QString usn = it.key();
00283 GetServerContent(usn, it.value(), list, mediaservers.get());
00284 }
00285 m_lock.unlock();
00286 }
00287
00288 bool UPNPScanner::GetMetadata(QVariant &data)
00289 {
00290
00291 if (!data.canConvert(QVariant::StringList))
00292 return false;
00293
00294 QStringList list = data.toStringList();
00295 if (list.size() != 2)
00296 return false;
00297
00298 QString usn = list[0];
00299 QString object = list[1];
00300
00301 m_lock.lock();
00302 bool valid = m_servers.contains(usn);
00303 if (valid)
00304 {
00305 MediaServerItem* item = m_servers[usn]->Find(object);
00306 valid = item ? !item->m_scanned : false;
00307 }
00308 m_lock.unlock();
00309 if (!valid)
00310 return false;
00311
00312 MythEvent *me = new MythEvent("UPNP_BROWSEOBJECT", list);
00313 qApp->postEvent(this, me);
00314
00315 int count = 0;
00316 bool found = false;
00317 LOG(VB_GENERAL, LOG_INFO, "START");
00318 while (!found && (count++ < 100))
00319 {
00320 usleep(100000);
00321 m_lock.lock();
00322 if (m_servers.contains(usn))
00323 {
00324 MediaServerItem *item = m_servers[usn]->Find(object);
00325 if (item)
00326 {
00327 found = item->m_scanned;
00328 }
00329 else
00330 {
00331 LOG(VB_GENERAL, LOG_INFO, QString("Item went away..."));
00332 found = true;
00333 }
00334 }
00335 else
00336 {
00337 LOG(VB_GENERAL, LOG_INFO,
00338 QString("Server went away while browsing."));
00339 found = true;
00340 }
00341 m_lock.unlock();
00342 }
00343 LOG(VB_GENERAL, LOG_INFO, "END");
00344 return true;
00345 }
00346
00352 void UPNPScanner::GetServerContent(QString &usn,
00353 MediaServerItem *content,
00354 VideoMetadataListManager::metadata_list* list,
00355 meta_dir_node *node)
00356 {
00357 if (!content->m_scanned)
00358 {
00359 smart_dir_node subnode = node->addSubDir(content->m_name);
00360
00361 QStringList data;
00362 data << usn;
00363 data << content->m_id;
00364 subnode->SetData(data);
00365
00366 VideoMetadataListManager::VideoMetadataPtr item(new VideoMetadata(QString()));
00367 item->SetTitle(QString("Dummy"));
00368 list->push_back(item);
00369 subnode->addEntry(smart_meta_node(new meta_data_node(item.get())));
00370 return;
00371 }
00372
00373 node->SetData(QVariant());
00374
00375 if (content->m_url.isEmpty())
00376 {
00377 smart_dir_node container = node->addSubDir(content->m_name);
00378 QMutableMapIterator<QString,MediaServerItem> it(content->m_children);
00379 while (it.hasNext())
00380 {
00381 it.next();
00382 GetServerContent(usn, &it.value(), list, container.get());
00383 }
00384 return;
00385 }
00386
00387 VideoMetadataListManager::VideoMetadataPtr item(new VideoMetadata(content->m_url));
00388 item->SetTitle(content->m_name);
00389 list->push_back(item);
00390 node->addEntry(smart_meta_node(new meta_data_node(item.get())));
00391 }
00392
00398 QMap<QString,QString> UPNPScanner::ServerList(void)
00399 {
00400 QMap<QString,QString> servers;
00401 m_lock.lock();
00402 QHashIterator<QString,MediaServer*> it(m_servers);
00403 while (it.hasNext())
00404 {
00405 it.next();
00406 servers.insert(it.key(), it.value()->m_friendlyName);
00407 }
00408 m_lock.unlock();
00409 return servers;
00410 }
00411
00417 void UPNPScanner::Start()
00418 {
00419 m_lock.lock();
00420
00421
00422 m_network = new QNetworkAccessManager();
00423 connect(m_network, SIGNAL(finished(QNetworkReply*)),
00424 this, SLOT(replyFinished(QNetworkReply*)));
00425
00426
00427 SSDP::Instance()->AddListener(this);
00428
00429
00430 if (m_subscription)
00431 m_subscription->addListener(this);
00432
00433
00434 m_updateTimer = new QTimer(this);
00435 m_updateTimer->setSingleShot(true);
00436 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(Update()));
00437
00438
00439 m_watchdogTimer = new QTimer(this);
00440 connect(m_watchdogTimer, SIGNAL(timeout()), this, SLOT(CheckStatus()));
00441 m_watchdogTimer->start(1000 * 10);
00442
00443
00444 m_masterHost = gCoreContext->GetSetting("MasterServerIP");
00445 m_masterPort = gCoreContext->GetSettingOnHost("BackendStatusPort",
00446 m_masterHost, "6544").toInt();
00447
00448 m_lock.unlock();
00449 LOG(VB_GENERAL, LOG_INFO, LOC + "Started");
00450 }
00451
00456 void UPNPScanner::Stop(void)
00457 {
00458 m_lock.lock();
00459
00460
00461 SSDP::Instance()->RemoveListener(this);
00462 if (m_subscription)
00463 m_subscription->removeListener(this);
00464
00465
00466 if (m_updateTimer)
00467 m_updateTimer->stop();
00468 if (m_watchdogTimer)
00469 m_watchdogTimer->stop();
00470
00471
00472 QHashIterator<QString,MediaServer*> it(m_servers);
00473 while (it.hasNext())
00474 {
00475 it.next();
00476 if (m_subscription && it.value()->m_subscribed)
00477 m_subscription->Unsubscribe(it.key());
00478 if (it.value()->m_renewalTimerId)
00479 killTimer(it.value()->m_renewalTimerId);
00480 delete it.value();
00481 }
00482 m_servers.clear();
00483
00484
00485 foreach (QNetworkReply *reply, m_descriptionRequests)
00486 {
00487 reply->abort();
00488 delete reply;
00489 }
00490 m_descriptionRequests.clear();
00491 foreach (QNetworkReply *reply, m_browseRequests)
00492 {
00493 reply->abort();
00494 delete reply;
00495 }
00496 m_browseRequests.clear();
00497 delete m_network;
00498 m_network = NULL;
00499
00500
00501 delete m_updateTimer;
00502 delete m_watchdogTimer;
00503 m_updateTimer = NULL;
00504 m_watchdogTimer = NULL;
00505
00506 m_lock.unlock();
00507 LOG(VB_GENERAL, LOG_INFO, LOC + "Finished");
00508 }
00509
00515 void UPNPScanner::Update(void)
00516 {
00517
00518 m_lock.lock();
00519 if (m_servers.isEmpty())
00520 {
00521 m_lock.unlock();
00522 return;
00523 }
00524
00525
00526 bool reschedule = false;
00527
00528 QHashIterator<QString,MediaServer*> it(m_servers);
00529 while (it.hasNext())
00530 {
00531 it.next();
00532 if ((it.value()->m_connectionAttempts < MAX_ATTEMPTS) &&
00533 (it.value()->m_controlURL.isEmpty()))
00534 {
00535 bool sent = false;
00536 QUrl url = it.value()->m_URL;
00537 if (!m_descriptionRequests.contains(url) &&
00538 (m_descriptionRequests.size() < MAX_REQUESTS) &&
00539 url.isValid())
00540 {
00541 QNetworkReply *reply = m_network->get(QNetworkRequest(url));
00542 if (reply)
00543 {
00544 sent = true;
00545 m_descriptionRequests.insert(url, reply);
00546 it.value()->m_connectionAttempts++;
00547 }
00548 }
00549 if (!sent)
00550 reschedule = true;
00551 }
00552 }
00553
00554 if (reschedule)
00555 ScheduleUpdate();
00556 m_lock.unlock();
00557 }
00558
00563 void UPNPScanner::CheckStatus(void)
00564 {
00565
00566
00567
00568 m_lock.lock();
00569 QMutableHashIterator<QString,MediaServer*> it(m_servers);
00570 while (it.hasNext())
00571 {
00572 it.next();
00573 if (!SSDP::Find("urn:schemas-upnp-org:device:MediaServer:1", it.key()))
00574 {
00575 LOG(VB_UPNP, LOG_INFO, LOC +
00576 QString("%1 no longer in SSDP cache. Removing")
00577 .arg(it.value()->m_URL.toString()));
00578 MediaServer* last = it.value();
00579 it.remove();
00580 delete last;
00581 }
00582 }
00583 m_lock.unlock();
00584 }
00585
00591 void UPNPScanner::replyFinished(QNetworkReply *reply)
00592 {
00593 if (!reply)
00594 return;
00595
00596 QUrl url = reply->url();
00597 bool valid = reply->error() == QNetworkReply::NoError;
00598
00599 if (!valid)
00600 {
00601 LOG(VB_UPNP, LOG_ERR, LOC +
00602 QString("Network request for '%1' returned error '%2'")
00603 .arg(url.toString()).arg(reply->errorString()));
00604 }
00605
00606 bool description = false;
00607 bool browse = false;
00608
00609 m_lock.lock();
00610 if (m_descriptionRequests.contains(url, reply))
00611 {
00612 m_descriptionRequests.remove(url, reply);
00613 description = true;
00614 }
00615 else if (m_browseRequests.contains(url, reply))
00616 {
00617 m_browseRequests.remove(url, reply);
00618 browse = true;
00619 }
00620 m_lock.unlock();
00621
00622 if (browse && valid)
00623 {
00624 ParseBrowse(url, reply);
00625 if (m_fullscan)
00626 BrowseNextContainer();
00627 }
00628 else if (description)
00629 {
00630 if (!valid || (valid && !ParseDescription(url, reply)))
00631 {
00632
00633 CheckFailure(url);
00634
00635 ScheduleUpdate();
00636 }
00637 }
00638 else
00639 LOG(VB_UPNP, LOG_ERR, LOC + "Received unknown reply");
00640
00641 reply->deleteLater();
00642 }
00643
00648 void UPNPScanner::customEvent(QEvent *event)
00649 {
00650 if ((MythEvent::Type)(event->type()) != MythEvent::MythEventMessage)
00651 return;
00652
00653
00654 MythEvent *me = (MythEvent *)event;
00655 QString ev = me->Message();
00656
00657 if (ev == "UPNP_STARTSCAN")
00658 {
00659 BrowseNextContainer();
00660 return;
00661 }
00662 else if (ev == "UPNP_BROWSEOBJECT")
00663 {
00664 if (me->ExtraDataCount() == 2)
00665 {
00666 QUrl url;
00667 QString usn = me->ExtraData(0);
00668 QString objectid = me->ExtraData(1);
00669 m_lock.lock();
00670 if (m_servers.contains(usn))
00671 {
00672 url = m_servers[usn]->m_controlURL;
00673 LOG(VB_GENERAL, LOG_INFO, QString("UPNP_BROWSEOBJECT: %1->%2")
00674 .arg(m_servers[usn]->m_friendlyName).arg(objectid));
00675 }
00676 m_lock.unlock();
00677 if (!url.isEmpty())
00678 SendBrowseRequest(url, objectid);
00679 }
00680 return;
00681 }
00682 else if (ev == "UPNP_EVENT")
00683 {
00684 MythInfoMapEvent *info = (MythInfoMapEvent*)event;
00685 if (!info)
00686 return;
00687 if (!info->InfoMap())
00688 return;
00689
00690 QString usn = info->InfoMap()->value("usn");
00691 QString id = info->InfoMap()->value("SystemUpdateID");
00692 if (usn.isEmpty() || id.isEmpty())
00693 return;
00694
00695 m_lock.lock();
00696 if (m_servers.contains(usn))
00697 {
00698 int newid = id.toInt();
00699 if (m_servers[usn]->m_systemUpdateID != newid)
00700 {
00701 m_scanComplete &= m_servers[usn]->ResetContent(newid);
00702 LOG(VB_GENERAL, LOG_INFO, LOC +
00703 QString("New SystemUpdateID '%1' for %2").arg(id).arg(usn));
00704 Debug();
00705 }
00706 }
00707 m_lock.unlock();
00708 return;
00709 }
00710
00711
00712 QString uri = me->ExtraDataCount() > 0 ? me->ExtraData(0) : QString();
00713 QString usn = me->ExtraDataCount() > 1 ? me->ExtraData(1) : QString();
00714
00715 if (uri == "urn:schemas-upnp-org:device:MediaServer:1")
00716 {
00717 QString url = (ev == "SSDP_ADD") ? me->ExtraData(2) : QString();
00718 AddServer(usn, url);
00719 }
00720 }
00721
00726 void UPNPScanner::timerEvent(QTimerEvent * event)
00727 {
00728 int id = event->timerId();
00729 if (id)
00730 killTimer(id);
00731
00732 int timeout = 0;
00733 QString usn;
00734
00735 m_lock.lock();
00736 QHashIterator<QString,MediaServer*> it(m_servers);
00737 while (it.hasNext())
00738 {
00739 it.next();
00740 if (it.value()->m_renewalTimerId == id)
00741 {
00742 it.value()->m_renewalTimerId = 0;
00743 usn = it.key();
00744 if (m_subscription)
00745 timeout = m_subscription->Renew(usn);
00746 }
00747 }
00748 m_lock.unlock();
00749
00750 if (timeout > 0)
00751 {
00752 ScheduleRenewal(usn, timeout);
00753 LOG(VB_GENERAL, LOG_INFO, LOC +
00754 QString("Re-subscribed for %1 seconds to %2")
00755 .arg(timeout).arg(usn));
00756 }
00757 }
00758
00762 void UPNPScanner::ScheduleUpdate(void)
00763 {
00764 m_lock.lock();
00765 if (m_updateTimer && !m_updateTimer->isActive())
00766 m_updateTimer->start(200);
00767 m_lock.unlock();
00768 }
00769
00774 void UPNPScanner::CheckFailure(const QUrl &url)
00775 {
00776 m_lock.lock();
00777 QHashIterator<QString,MediaServer*> it(m_servers);
00778 while (it.hasNext())
00779 {
00780 it.next();
00781 if (it.value()->m_URL == url &&
00782 it.value()->m_connectionAttempts == MAX_ATTEMPTS)
00783 {
00784 Debug();
00785 break;
00786 }
00787 }
00788 m_lock.unlock();
00789 }
00790
00794 void UPNPScanner::Debug(void)
00795 {
00796 m_lock.lock();
00797 LOG(VB_UPNP, LOG_INFO, LOC + QString("%1 media servers discovered:")
00798 .arg(m_servers.size()));
00799 QHashIterator<QString,MediaServer*> it(m_servers);
00800 while (it.hasNext())
00801 {
00802 it.next();
00803 QString status = "Probing";
00804 if (it.value()->m_controlURL.toString().isEmpty())
00805 {
00806 if (it.value()->m_connectionAttempts >= MAX_ATTEMPTS)
00807 status = "Failed";
00808 }
00809 else
00810 status = "Yes";
00811 LOG(VB_UPNP, LOG_INFO, LOC +
00812 QString("'%1' Connected: %2 Subscribed: %3 SystemUpdateID: "
00813 "%4 timerId: %5")
00814 .arg(it.value()->m_friendlyName).arg(status)
00815 .arg(it.value()->m_subscribed ? "Yes" : "No")
00816 .arg(it.value()->m_systemUpdateID)
00817 .arg(it.value()->m_renewalTimerId));
00818 }
00819 m_lock.unlock();
00820 }
00821
00830 void UPNPScanner::BrowseNextContainer(void)
00831 {
00832 QMutexLocker locker(&m_lock);
00833
00834 QHashIterator<QString,MediaServer*> it(m_servers);
00835 bool complete = true;
00836 while (it.hasNext())
00837 {
00838 it.next();
00839 if (it.value()->m_subscribed)
00840 {
00841
00842 if (m_browseRequests.contains(it.value()->m_controlURL))
00843 {
00844 complete = false;
00845 continue;
00846 }
00847
00848 QString next = it.value()->NextUnbrowsed();
00849 if (!next.isEmpty())
00850 {
00851 complete = false;
00852 SendBrowseRequest(it.value()->m_controlURL, next);
00853 continue;
00854 }
00855
00856 LOG(VB_UPNP, LOG_INFO, LOC + QString("Scan completed for %1")
00857 .arg(it.value()->m_friendlyName));
00858 }
00859 }
00860
00861 if (complete)
00862 {
00863 LOG(VB_GENERAL, LOG_INFO, LOC +
00864 QString("Media Server scan is complete."));
00865 m_scanComplete = true;
00866 m_fullscan = false;
00867 }
00868 }
00869
00875 void UPNPScanner::SendBrowseRequest(const QUrl &url, const QString &objectid)
00876 {
00877 QNetworkRequest req = QNetworkRequest(url);
00878 req.setRawHeader("CONTENT-TYPE", "text/xml; charset=\"utf-8\"");
00879 req.setRawHeader("SOAPACTION",
00880 "\"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"");
00881 #if 0
00882 req.setRawHeader("MAN", "\"http://schemasxmlsoap.org/soap/envelope/\"");
00883 req.setRawHeader("01-SOAPACTION",
00884 "\"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"");
00885 #endif
00886
00887 QByteArray body;
00888 QTextStream data(&body);
00889 data.setCodec(QTextCodec::codecForName("UTF-8"));
00890 data << "<?xml version=\"1.0\"?>\r\n";
00891 data << "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n";
00892 data << " <s:Body>\r\n";
00893 data << " <u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">\r\n";
00894 data << " <ObjectID>" << objectid.toUtf8() << "</ObjectID>\r\n";
00895 data << " <BrowseFlag>BrowseDirectChildren</BrowseFlag>\r\n";
00896 data << " <Filter>*</Filter>\r\n";
00897 data << " <StartingIndex>0</StartingIndex>\r\n";
00898 data << " <RequestedCount>0</RequestedCount>\r\n";
00899 data << " <SortCriteria></SortCriteria>\r\n";
00900 data << " </u:Browse>\r\n";
00901 data << " </s:Body>\r\n";
00902 data << "</s:Envelope>\r\n";
00903 data.flush();
00904
00905 m_lock.lock();
00906 QNetworkReply *reply = m_network->post(req, body);
00907 if (reply)
00908 m_browseRequests.insert(url, reply);
00909 m_lock.unlock();
00910 }
00911
00917 void UPNPScanner::AddServer(const QString &usn, const QString &url)
00918 {
00919 if (url.isEmpty())
00920 {
00921 RemoveServer(usn);
00922 return;
00923 }
00924
00925
00926 if (m_masterHost.isEmpty())
00927 {
00928 m_masterHost = gCoreContext->GetSetting("MasterServerIP");
00929 m_masterPort = gCoreContext->GetSettingOnHost("BackendStatusPort",
00930 m_masterHost, "6544").toInt();
00931 }
00932
00933 QUrl qurl(url);
00934 if (qurl.host() == m_masterHost && qurl.port() == m_masterPort)
00935 {
00936 LOG(VB_UPNP, LOG_INFO, LOC + "Ignoring master backend.");
00937 return;
00938 }
00939
00940 m_lock.lock();
00941 if (!m_servers.contains(usn))
00942 {
00943 m_servers.insert(usn, new MediaServer(url));
00944 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Adding: %1").arg(usn));
00945 ScheduleUpdate();
00946 }
00947 m_lock.unlock();
00948 }
00949
00953 void UPNPScanner::RemoveServer(const QString &usn)
00954 {
00955 m_lock.lock();
00956 if (m_servers.contains(usn))
00957 {
00958 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing: %1").arg(usn));
00959 MediaServer* old = m_servers[usn];
00960 if (old->m_renewalTimerId)
00961 killTimer(old->m_renewalTimerId);
00962 m_servers.remove(usn);
00963 delete old;
00964 if (m_subscription)
00965 m_subscription->Remove(usn);
00966 }
00967 m_lock.unlock();
00968
00969 Debug();
00970 }
00971
00976 void UPNPScanner::ScheduleRenewal(const QString &usn, int timeout)
00977 {
00978
00979 int renew = timeout - 10;
00980 if (renew < 10)
00981 renew = 10;
00982 if (renew > 43200)
00983 renew = 43200;
00984
00985 m_lock.lock();
00986 if (m_servers.contains(usn))
00987 m_servers[usn]->m_renewalTimerId = startTimer(renew * 1000);
00988 m_lock.unlock();
00989 }
00990
00995 void UPNPScanner::ParseBrowse(const QUrl &url, QNetworkReply *reply)
00996 {
00997 QByteArray data = reply->readAll();
00998 if (data.isEmpty())
00999 return;
01000
01001
01002 QDomDocument *parent = new QDomDocument();
01003 QString errorMessage;
01004 int errorLine = 0;
01005 int errorColumn = 0;
01006 if (!parent->setContent(data, false, &errorMessage, &errorLine,
01007 &errorColumn))
01008 {
01009 LOG(VB_GENERAL, LOG_ERR, LOC +
01010 QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
01011 .arg(errorLine).arg(errorColumn).arg(errorMessage));
01012 delete parent;
01013 return;
01014 }
01015
01016 LOG(VB_UPNP, LOG_INFO, "\n\n" + parent->toString(4) + "\n\n");
01017
01018
01019 QDomDocument *result = NULL;
01020 uint num = 0;
01021 uint total = 0;
01022 uint updateid = 0;
01023 QDomElement docElem = parent->documentElement();
01024 QDomNode n = docElem.firstChild();
01025 if (!n.isNull())
01026 result = FindResult(n, num, total, updateid);
01027 delete parent;
01028
01029 if (!result || num < 1 || total < 1)
01030 {
01031 LOG(VB_GENERAL, LOG_ERR, LOC +
01032 QString("Failed to find result for %1") .arg(url.toString()));
01033 return;
01034 }
01035
01036
01037 m_lock.lock();
01038
01039 MediaServer* server = NULL;
01040 QHashIterator<QString,MediaServer*> it(m_servers);
01041 while (it.hasNext())
01042 {
01043 it.next();
01044 if (url == it.value()->m_controlURL)
01045 {
01046 server = it.value();
01047 break;
01048 }
01049 }
01050
01051
01052 if (!server)
01053 {
01054 m_lock.unlock();
01055 LOG(VB_GENERAL, LOG_ERR, LOC +
01056 QString("Received unknown response for %1").arg(url.toString()));
01057 return;
01058 }
01059
01060
01061 if (server->m_systemUpdateID != (int)updateid)
01062 {
01063
01064
01065 LOG(VB_GENERAL, LOG_ERR, LOC +
01066 QString("%1 updateID changed during browse (old %2 new %3)")
01067 .arg(server->m_friendlyName).arg(server->m_systemUpdateID)
01068 .arg(updateid));
01069 m_scanComplete &= server->ResetContent(updateid);
01070 Debug();
01071 }
01072
01073
01074
01075 bool reset = true;
01076 docElem = result->documentElement();
01077 n = docElem.firstChild();
01078 while (!n.isNull())
01079 {
01080 FindItems(n, *server, reset);
01081 n = n.nextSibling();
01082 }
01083 delete result;
01084
01085 m_lock.unlock();
01086 }
01087
01088 void UPNPScanner::FindItems(const QDomNode &n, MediaServerItem &content,
01089 bool &resetparent)
01090 {
01091 QDomElement node = n.toElement();
01092 if (node.isNull())
01093 return;
01094
01095 if (node.tagName() == "container")
01096 {
01097 QString title = "ERROR";
01098 QDomNode next = node.firstChild();
01099 while (!next.isNull())
01100 {
01101 QDomElement container = next.toElement();
01102 if (!container.isNull() && container.tagName() == "title")
01103 title = container.text();
01104 next = next.nextSibling();
01105 }
01106
01107 QString thisid = node.attribute("id", "ERROR");
01108 QString parentid = node.attribute("parentID", "ERROR");
01109 MediaServerItem container =
01110 MediaServerItem(thisid, parentid, title, QString());
01111 MediaServerItem *parent = content.Find(parentid);
01112 if (parent)
01113 {
01114 if (resetparent)
01115 {
01116 parent->Reset();
01117 resetparent = false;
01118 }
01119 parent->m_scanned = true;
01120 parent->Add(container);
01121 }
01122 return;
01123 }
01124
01125 if (node.tagName() == "item")
01126 {
01127 QString title = "ERROR";
01128 QString url = "ERROR";
01129 QDomNode next = node.firstChild();
01130 while (!next.isNull())
01131 {
01132 QDomElement item = next.toElement();
01133 if (!item.isNull())
01134 {
01135 if(item.tagName() == "res")
01136 url = item.text();
01137 if(item.tagName() == "title")
01138 title = item.text();
01139 }
01140 next = next.nextSibling();
01141 }
01142
01143 QString thisid = node.attribute("id", "ERROR");
01144 QString parentid = node.attribute("parentID", "ERROR");
01145 MediaServerItem item =
01146 MediaServerItem(thisid, parentid, title, url);
01147 item.m_scanned = true;
01148 MediaServerItem *parent = content.Find(parentid);
01149 if (parent)
01150 {
01151 if (resetparent)
01152 {
01153 parent->Reset();
01154 resetparent = false;
01155 }
01156 parent->m_scanned = true;
01157 parent->Add(item);
01158 }
01159 return;
01160 }
01161
01162 QDomNode next = node.firstChild();
01163 while (!next.isNull())
01164 {
01165 FindItems(next, content, resetparent);
01166 next = next.nextSibling();
01167 }
01168 }
01169
01170 QDomDocument* UPNPScanner::FindResult(const QDomNode &n, uint &num,
01171 uint &total, uint &updateid)
01172 {
01173 QDomDocument *result = NULL;
01174 QDomElement node = n.toElement();
01175 if (node.isNull())
01176 return NULL;
01177
01178 if (node.tagName() == "NumberReturned")
01179 num = node.text().toUInt();
01180 if (node.tagName() == "TotalMatches")
01181 total = node.text().toUInt();
01182 if (node.tagName() == "UpdateID")
01183 updateid = node.text().toUInt();
01184 if (node.tagName() == "Result" && !result)
01185 {
01186 QString errorMessage;
01187 int errorLine = 0;
01188 int errorColumn = 0;
01189 result = new QDomDocument();
01190 if (!result->setContent(node.text(), true, &errorMessage, &errorLine, &errorColumn))
01191 {
01192 LOG(VB_GENERAL, LOG_ERR, LOC +
01193 QString("DIDL Parse error, Line: %1 Col: %2 Error: '%3'")
01194 .arg(errorLine).arg(errorColumn).arg(errorMessage));
01195 delete result;
01196 result = NULL;
01197 }
01198 }
01199
01200 QDomNode next = node.firstChild();
01201 while (!next.isNull())
01202 {
01203 QDomDocument *res = FindResult(next, num, total, updateid);
01204 if (res)
01205 result = res;
01206 next = next.nextSibling();
01207 }
01208 return result;
01209 }
01210
01215 bool UPNPScanner::ParseDescription(const QUrl &url, QNetworkReply *reply)
01216 {
01217 if (url.isEmpty() || !reply)
01218 return false;
01219
01220 QByteArray data = reply->readAll();
01221 if (data.isEmpty())
01222 {
01223 LOG(VB_GENERAL, LOG_ERR, LOC +
01224 QString("%1 returned an empty device description.")
01225 .arg(url.toString()));
01226 return false;
01227 }
01228
01229
01230 QString controlURL = QString();
01231 QString eventURL = QString();
01232 QString friendlyName = QString("Unknown");
01233 QString URLBase = QString();
01234
01235 QDomDocument doc;
01236 QString errorMessage;
01237 int errorLine = 0;
01238 int errorColumn = 0;
01239 if (!doc.setContent(data, false, &errorMessage, &errorLine, &errorColumn))
01240 {
01241 LOG(VB_GENERAL, LOG_ERR, LOC +
01242 QString("Failed to parse device description from %1")
01243 .arg(url.toString()));
01244 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Line: %1 Col: %2 Error: '%3'")
01245 .arg(errorLine).arg(errorColumn).arg(errorMessage));
01246 return false;
01247 }
01248
01249 QDomElement docElem = doc.documentElement();
01250 QDomNode n = docElem.firstChild();
01251 while (!n.isNull())
01252 {
01253 QDomElement e1 = n.toElement();
01254 if (!e1.isNull())
01255 {
01256 if(e1.tagName() == "device")
01257 ParseDevice(e1, controlURL, eventURL, friendlyName);
01258 if (e1.tagName() == "URLBase")
01259 URLBase = e1.text();
01260 }
01261 n = n.nextSibling();
01262 }
01263
01264 if (controlURL.isEmpty())
01265 {
01266 LOG(VB_UPNP, LOG_ERR, LOC +
01267 QString("Failed to parse device description for %1")
01268 .arg(url.toString()));
01269 return false;
01270 }
01271
01272
01273 if (URLBase.isEmpty())
01274 URLBase = url.toString(QUrl::RemovePath | QUrl::RemoveFragment |
01275 QUrl::RemoveQuery);
01276
01277
01278 while (!controlURL.isEmpty() && controlURL.left(1) == "/")
01279 controlURL = controlURL.mid(1);
01280
01281
01282
01283
01284
01285
01286 while (!URLBase.isEmpty() && URLBase.right(1) == "/")
01287 URLBase = URLBase.mid(0, URLBase.size() - 1);
01288
01289 controlURL = URLBase + "/" + controlURL;
01290 QString fulleventURL = URLBase + "/" + eventURL;
01291
01292 LOG(VB_UPNP, LOG_INFO, LOC + QString("Control URL for %1 at %2")
01293 .arg(friendlyName).arg(controlURL));
01294 LOG(VB_UPNP, LOG_INFO, LOC + QString("Event URL for %1 at %2")
01295 .arg(friendlyName).arg(fulleventURL));
01296
01297
01298
01299 QString usn;
01300 QUrl qeventurl = QUrl(fulleventURL);
01301 int timeout = 0;
01302
01303 m_lock.lock();
01304 QHashIterator<QString,MediaServer*> it(m_servers);
01305 while (it.hasNext())
01306 {
01307 it.next();
01308 if (it.value()->m_URL == url)
01309 {
01310 usn = it.key();
01311 QUrl qcontrolurl(controlURL);
01312 it.value()->m_controlURL = qcontrolurl;
01313 it.value()->m_eventSubURL = qeventurl;
01314 it.value()->m_eventSubPath = eventURL;
01315 it.value()->m_friendlyName = friendlyName;
01316 it.value()->m_name = friendlyName;
01317 break;
01318 }
01319 }
01320
01321 if (m_subscription && !usn.isEmpty())
01322 {
01323 timeout = m_subscription->Subscribe(usn, qeventurl, eventURL);
01324 m_servers[usn]->m_subscribed = (timeout > 0);
01325 }
01326 m_lock.unlock();
01327
01328 if (timeout > 0)
01329 {
01330 LOG(VB_GENERAL, LOG_INFO, LOC +
01331 QString("Subscribed for %1 seconds to %2") .arg(timeout).arg(usn));
01332 ScheduleRenewal(usn, timeout);
01333
01334
01335 m_scanComplete = false;
01336 }
01337
01338 Debug();
01339 return true;
01340 }
01341
01342
01343 void UPNPScanner::ParseDevice(QDomElement &element, QString &controlURL,
01344 QString &eventURL, QString &friendlyName)
01345 {
01346 QDomNode dev = element.firstChild();
01347 while (!dev.isNull())
01348 {
01349 QDomElement e = dev.toElement();
01350 if (!e.isNull())
01351 {
01352 if (e.tagName() == "friendlyName")
01353 friendlyName = e.text();
01354 if (e.tagName() == "serviceList")
01355 ParseServiceList(e, controlURL, eventURL);
01356 }
01357 dev = dev.nextSibling();
01358 }
01359 }
01360
01361 void UPNPScanner::ParseServiceList(QDomElement &element, QString &controlURL,
01362 QString &eventURL)
01363 {
01364 QDomNode list = element.firstChild();
01365 while (!list.isNull())
01366 {
01367 QDomElement e = list.toElement();
01368 if (!e.isNull())
01369 if (e.tagName() == "service")
01370 ParseService(e, controlURL, eventURL);
01371 list = list.nextSibling();
01372 }
01373 }
01374
01375 void UPNPScanner::ParseService(QDomElement &element, QString &controlURL,
01376 QString &eventURL)
01377 {
01378 bool iscds = false;
01379 QString control_url = QString();
01380 QString event_url = QString();
01381 QDomNode service = element.firstChild();
01382
01383 while (!service.isNull())
01384 {
01385 QDomElement e = service.toElement();
01386 if (!e.isNull())
01387 {
01388 if (e.tagName() == "serviceType")
01389 iscds = (e.text() == "urn:schemas-upnp-org:service:ContentDirectory:1");
01390 if (e.tagName() == "controlURL")
01391 control_url = e.text();
01392 if (e.tagName() == "eventSubURL")
01393 event_url = e.text();
01394 }
01395 service = service.nextSibling();
01396 }
01397
01398 if (iscds)
01399 {
01400 controlURL = control_url;
01401 eventURL = event_url;
01402 }
01403 }