Skip to content

Commit

Permalink
UPNPScanner: Add an incremental browse mode.
Browse files Browse the repository at this point in the history
This will become the default behaviour, with the old full scan retained
for the time being at least. A UI client should now call
GetInitialMetadata which will populate the metadatalist with all known
media servers and any cached content. Additional metadata can then be
requested via GetMetaData(QVariant).
  • Loading branch information
Mark Kendall committed Nov 6, 2011
1 parent c33a386 commit c7774aa
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 32 deletions.
230 changes: 200 additions & 30 deletions mythtv/programs/mythfrontend/upnpscanner.cpp
Expand Up @@ -20,10 +20,7 @@ QString MediaServerItem::NextUnbrowsed(void)


// scan this container // scan this container
if (!m_scanned) if (!m_scanned)
{
m_scanned = true;
return m_id; return m_id;
}


// scan children // scan children
QMutableMapIterator<QString,MediaServerItem> it(m_children); QMutableMapIterator<QString,MediaServerItem> it(m_children);
Expand All @@ -38,22 +35,30 @@ QString MediaServerItem::NextUnbrowsed(void)
return QString(); return QString();
} }


bool MediaServerItem::Add(MediaServerItem &item) MediaServerItem* MediaServerItem::Find(QString &id)
{ {
if (m_id == item.m_parentid) if (m_id == id)
{ return this;
m_children.insert(item.m_id, item);
return true;
}


QMutableMapIterator<QString,MediaServerItem> it(m_children); QMutableMapIterator<QString,MediaServerItem> it(m_children);
while (it.hasNext()) while (it.hasNext())
{ {
it.next(); it.next();
if (it.value().Add(item)) MediaServerItem* result = it.value().Find(id);
return true; if (result)
return result;
} }


return NULL;
}

bool MediaServerItem::Add(MediaServerItem &item)
{
if (m_id == item.m_parentid)
{
m_children.insert(item.m_id, item);
return true;
}
return false; return false;
} }


Expand Down Expand Up @@ -128,7 +133,8 @@ QMutex* UPNPScanner::gUPNPScannerLock = new QMutex(QMutex::Recursive);
UPNPScanner::UPNPScanner(UPNPSubscription *sub) UPNPScanner::UPNPScanner(UPNPSubscription *sub)
: QObject(), m_subscription(sub), m_lock(QMutex::Recursive), : QObject(), m_subscription(sub), m_lock(QMutex::Recursive),
m_network(NULL), m_updateTimer(NULL), m_watchdogTimer(NULL), m_network(NULL), m_updateTimer(NULL), m_watchdogTimer(NULL),
m_masterHost(QString()), m_masterPort(0), m_scanComplete(false) m_masterHost(QString()), m_masterPort(0), m_scanComplete(false),
m_fullscan(false)
{ {
} }


Expand Down Expand Up @@ -193,14 +199,49 @@ UPNPScanner* UPNPScanner::Instance(UPNPSubscription *sub)
*/ */
void UPNPScanner::StartFullScan(void) void UPNPScanner::StartFullScan(void)
{ {
m_fullscan = true;
MythEvent *me = new MythEvent(QString("UPNP_STARTSCAN")); MythEvent *me = new MythEvent(QString("UPNP_STARTSCAN"));
qApp->postEvent(this, me); qApp->postEvent(this, me);
} }


/**
* \fn UPNPScanner::GetInitialMetadata
* Fill the given metadata_list and meta_dir_node with the root media
* server metadata (i.e. the MediaServers) and any additional metadata that
* that has already been scanned and cached.
*/
void UPNPScanner::GetInitialMetadata(VideoMetadataListManager::metadata_list* list,
meta_dir_node *node)
{
// nothing to see..
QMap<QString,QString> servers = ServerList();
if (servers.isEmpty())
return;

// Add MediaServers
LOG(VB_GENERAL, LOG_INFO, QString("Adding MediaServer metadata."));

smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
mediaservers->setPathRoot();

m_lock.lock();
QMutableHashIterator<QString,MediaServer*> it(m_servers);
while (it.hasNext())
{
it.next();
if (!it.value()->m_subscribed)
continue;

QString usn = it.key();
GetServerContent(usn, it.value(), list, mediaservers.get());
}
m_lock.unlock();
}

/** /**
* \fn UPNPScanner::GetMetadata * \fn UPNPScanner::GetMetadata
* Fill the given metadata_list and meta_dir_node with the metadata * Fill the given metadata_list and meta_dir_node with the metadata
* of content retrieved from known media servers. * of content retrieved from known media servers. A full scan is triggered.
*/ */
void UPNPScanner::GetMetadata(VideoMetadataListManager::metadata_list* list, void UPNPScanner::GetMetadata(VideoMetadataListManager::metadata_list* list,
meta_dir_node *node) meta_dir_node *node)
Expand Down Expand Up @@ -228,33 +269,117 @@ void UPNPScanner::GetMetadata(VideoMetadataListManager::metadata_list* list,




smart_dir_node mediaservers = node->addSubDir(tr("Media Servers")); smart_dir_node mediaservers = node->addSubDir(tr("Media Servers"));
mediaservers->setPathRoot();

m_lock.lock(); m_lock.lock();
QMutableHashIterator<QString,MediaServer*> it(m_servers); QMutableHashIterator<QString,MediaServer*> it(m_servers);
while (it.hasNext()) while (it.hasNext())
{ {
it.next(); it.next();
GetServerContent(it.value(), list, mediaservers.get()); if (!it.value()->m_subscribed)
continue;

QString usn = it.key();
GetServerContent(usn, it.value(), list, mediaservers.get());
}
m_lock.unlock();
}

bool UPNPScanner::GetMetadata(QVariant &data)
{
// we need a USN and objectID
if (!data.canConvert(QVariant::StringList))
return false;

QStringList list = data.toStringList();
if (list.size() != 2)
return false;

QString usn = list[0];
QString object = list[1];

m_lock.lock();
bool valid = m_servers.contains(usn);
if (valid)
{
MediaServerItem* item = m_servers[usn]->Find(object);
valid = item ? !item->m_scanned : false;
} }
m_lock.unlock(); m_lock.unlock();
if (!valid)
return false;

MythEvent *me = new MythEvent("UPNP_BROWSEOBJECT", list);
qApp->postEvent(this, me);

int count = 0;
bool found = false;
LOG(VB_GENERAL, LOG_INFO, "START");
while (!found && (count++ < 100)) // 10 seconds
{
usleep(100000);
m_lock.lock();
if (m_servers.contains(usn))
{
MediaServerItem *item = m_servers[usn]->Find(object);
if (item)
{
found = item->m_scanned;
}
else
{
LOG(VB_GENERAL, LOG_INFO, QString("Item went away..."));
found = true;
}
}
else
{
LOG(VB_GENERAL, LOG_INFO,
QString("Server went away while browsing."));
found = true;
}
m_lock.unlock();
}
LOG(VB_GENERAL, LOG_INFO, "END");
return true;
} }


/** /**
* \fn UPNPScanner::GetServerContent * \fn UPNPScanner::GetServerContent
* Recursively search a MediaServerItem for video metadata and add it to * Recursively search a MediaServerItem for video metadata and add it to
* the metadata_list and meta_dir_node. * the metadata_list and meta_dir_node.
*/ */
void UPNPScanner::GetServerContent(MediaServerItem *content, void UPNPScanner::GetServerContent(QString &usn,
MediaServerItem *content,
VideoMetadataListManager::metadata_list* list, VideoMetadataListManager::metadata_list* list,
meta_dir_node *node) meta_dir_node *node)
{ {
if (!content->m_scanned)
{
smart_dir_node subnode = node->addSubDir(content->m_name);

QStringList data;
data << usn;
data << content->m_id;
subnode->SetData(data);

VideoMetadataListManager::VideoMetadataPtr item(new VideoMetadata(QString()));
item->SetTitle(QString("Dummy"));
list->push_back(item);
subnode->addEntry(smart_meta_node(new meta_data_node(item.get())));
return;
}

node->SetData(QVariant());

if (content->m_url.isEmpty()) if (content->m_url.isEmpty())
{ {
smart_dir_node container = node->addSubDir(content->m_name); smart_dir_node container = node->addSubDir(content->m_name);
QMutableMapIterator<QString,MediaServerItem> it(content->m_children); QMutableMapIterator<QString,MediaServerItem> it(content->m_children);
while (it.hasNext()) while (it.hasNext())
{ {
it.next(); it.next();
GetServerContent(&it.value(), list, container.get()); GetServerContent(usn, &it.value(), list, container.get());
} }
return; return;
} }
Expand Down Expand Up @@ -491,8 +616,8 @@ void UPNPScanner::replyFinished(QNetworkReply *reply)
if (browse && valid) if (browse && valid)
{ {
ParseBrowse(url, reply); ParseBrowse(url, reply);
// a complete scan is event driven, so trigger the next browse if (m_fullscan)
BrowseNextContainer(); BrowseNextContainer();
} }
else if (description) else if (description)
{ {
Expand Down Expand Up @@ -528,6 +653,26 @@ void UPNPScanner::customEvent(QEvent *event)
BrowseNextContainer(); BrowseNextContainer();
return; return;
} }
else if (ev == "UPNP_BROWSEOBJECT")
{
if (me->ExtraDataCount() == 2)
{
QUrl url;
QString usn = me->ExtraData(0);
QString objectid = me->ExtraData(1);
m_lock.lock();
if (m_servers.contains(usn))
{
url = m_servers[usn]->m_controlURL;
LOG(VB_GENERAL, LOG_INFO, QString("UPNP_BROWSEOBJECT: %1->%2")
.arg(m_servers[usn]->m_friendlyName).arg(objectid));
}
m_lock.unlock();
if (!url.isEmpty())
SendBrowseRequest(url, objectid);
}
return;
}
else if (ev == "UPNP_EVENT") else if (ev == "UPNP_EVENT")
{ {
MythInfoMapEvent *info = (MythInfoMapEvent*)event; MythInfoMapEvent *info = (MythInfoMapEvent*)event;
Expand Down Expand Up @@ -712,6 +857,7 @@ void UPNPScanner::BrowseNextContainer(void)
LOG(VB_GENERAL, LOG_INFO, LOC + LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Media Server scan is complete.")); QString("Media Server scan is complete."));
m_scanComplete = true; m_scanComplete = true;
m_fullscan = false;
} }
} }


Expand Down Expand Up @@ -918,20 +1064,23 @@ void UPNPScanner::ParseBrowse(const QUrl &url, QNetworkReply *reply)
Debug(); Debug();
} }


// find containers (directories) and actual items and add them // find containers (directories) and actual items and add them and reset
// the parent when we have found the first item
bool reset = true;
docElem = result->documentElement(); docElem = result->documentElement();
n = docElem.firstChild(); n = docElem.firstChild();
while (!n.isNull()) while (!n.isNull())
{ {
FindItems(n, *server); FindItems(n, *server, reset);
n = n.nextSibling(); n = n.nextSibling();
} }
delete result; delete result;


m_lock.unlock(); m_lock.unlock();
} }


void UPNPScanner::FindItems(const QDomNode &n, MediaServerItem &content) void UPNPScanner::FindItems(const QDomNode &n, MediaServerItem &content,
bool &resetparent)
{ {
QDomElement node = n.toElement(); QDomElement node = n.toElement();
if (node.isNull()) if (node.isNull())
Expand All @@ -949,11 +1098,21 @@ void UPNPScanner::FindItems(const QDomNode &n, MediaServerItem &content)
next = next.nextSibling(); next = next.nextSibling();
} }


QString thisid = node.attribute("id", "ERROR");
QString parentid = node.attribute("parentID", "ERROR");
MediaServerItem container = MediaServerItem container =
MediaServerItem(node.attribute("id", "ERROR"), MediaServerItem(thisid, parentid, title, QString());
node.attribute("parentID", "ERROR"), MediaServerItem *parent = content.Find(parentid);
title, QString()); if (parent)
content.Add(container); {
if (resetparent)
{
parent->Reset();
resetparent = false;
}
parent->m_scanned = true;
parent->Add(container);
}
return; return;
} }


Expand All @@ -975,18 +1134,29 @@ void UPNPScanner::FindItems(const QDomNode &n, MediaServerItem &content)
next = next.nextSibling(); next = next.nextSibling();
} }


QString thisid = node.attribute("id", "ERROR");
QString parentid = node.attribute("parentID", "ERROR");
MediaServerItem item = MediaServerItem item =
MediaServerItem(node.attribute("id", "ERROR"), MediaServerItem(thisid, parentid, title, url);
node.attribute("parentID", "ERROR"), item.m_scanned = true;
title, url); MediaServerItem *parent = content.Find(parentid);
content.Add(item); if (parent)
{
if (resetparent)
{
parent->Reset();
resetparent = false;
}
parent->m_scanned = true;
parent->Add(item);
}
return; return;
} }


QDomNode next = node.firstChild(); QDomNode next = node.firstChild();
while (!next.isNull()) while (!next.isNull())
{ {
FindItems(next, content); FindItems(next, content, resetparent);
next = next.nextSibling(); next = next.nextSibling();
} }
} }
Expand Down

0 comments on commit c7774aa

Please sign in to comment.