|
MythTV
0.26-pre
|
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 // items don't need scanning 00018 if (!m_url.isEmpty()) 00019 return QString(); 00020 00021 // scan this container 00022 if (!m_scanned) 00023 return m_id; 00024 00025 // scan children 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 // nothing to see.. 00217 QMap<QString,QString> servers = ServerList(); 00218 if (servers.isEmpty()) 00219 return; 00220 00221 // Add MediaServers 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 // nothing to see.. 00250 QMap<QString,QString> servers = ServerList(); 00251 if (servers.isEmpty()) 00252 return; 00253 00254 // Start scanning if it isn't already running 00255 StartFullScan(); 00256 00257 // wait for the scanner to complete - with a 30 second timeout 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 // some scans may just take too long (PlayOn) 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 // we need a USN and objectID 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)) // 10 seconds 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 // create our network handler 00422 m_network = new QNetworkAccessManager(); 00423 connect(m_network, SIGNAL(finished(QNetworkReply*)), 00424 this, SLOT(replyFinished(QNetworkReply*))); 00425 00426 // listen for SSDP updates 00427 SSDP::Instance()->AddListener(this); 00428 00429 // listen for subscriptions and events 00430 if (m_subscription) 00431 m_subscription->addListener(this); 00432 00433 // create our update timer (driven by AddServer and ParseDescription) 00434 m_updateTimer = new QTimer(this); 00435 m_updateTimer->setSingleShot(true); 00436 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(Update())); 00437 00438 // create our watchdog timer (checks for stale servers) 00439 m_watchdogTimer = new QTimer(this); 00440 connect(m_watchdogTimer, SIGNAL(timeout()), this, SLOT(CheckStatus())); 00441 m_watchdogTimer->start(1000 * 10); // every 10s 00442 00443 // avoid connecting to the master backend 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 // stop listening 00461 SSDP::Instance()->RemoveListener(this); 00462 if (m_subscription) 00463 m_subscription->removeListener(this); 00464 00465 // disable updates 00466 if (m_updateTimer) 00467 m_updateTimer->stop(); 00468 if (m_watchdogTimer) 00469 m_watchdogTimer->stop(); 00470 00471 // cleanup our servers and subscriptions 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 // cleanup the network 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 // delete the timers 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 // decide which servers still need to be checked 00518 m_lock.lock(); 00519 if (m_servers.isEmpty()) 00520 { 00521 m_lock.unlock(); 00522 return; 00523 } 00524 00525 // if our network queue is full, then we may need to come back later 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 // FIXME 00566 // Remove stale servers - the SSDP cache code does not send out removal 00567 // notifications for expired (rather than explicitly closed) connections 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 // if there will be no more attempts, update the logs 00633 CheckFailure(url); 00634 // try again 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 // UPnP events 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 // process SSDP cache updates 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 // limit browse requests to one active per server 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 // sometimes initialisation is too early and m_masterHost is empty 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 // sanitise the timeout 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 // Open the response for parsing 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 // pull out the actual result 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 // determine the 'server' which requested the browse 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 // discard unmatched responses 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 // check the update ID 01061 if (server->m_systemUpdateID != (int)updateid) 01062 { 01063 // if this is not the root container, this browse will now fail 01064 // as the appropriate parentID will not be found 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 // find containers (directories) and actual items and add them and reset 01074 // the parent when we have found the first item 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 // parse the device description 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 // if no URLBase was provided, use the known url 01273 if (URLBase.isEmpty()) 01274 URLBase = url.toString(QUrl::RemovePath | QUrl::RemoveFragment | 01275 QUrl::RemoveQuery); 01276 01277 // strip leading slashes off the controlURL 01278 while (!controlURL.isEmpty() && controlURL.left(1) == "/") 01279 controlURL = controlURL.mid(1); 01280 01281 // strip leading slashes off the eventURL 01282 //while (!eventURL.isEmpty() && eventURL.left(1) == "/") 01283 // eventURL = eventURL.mid(1); 01284 01285 // strip trailing slashes off URLBase 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 // update the server details. If the server has gone away since the request 01298 // was posted, this will silently fail and we won't try again 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 // we only scan servers we are subscribed to - and the scan is now 01334 // incomplete 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 }
1.7.6.1