upnp_intel.cpp 28.8 KB
Newer Older
1 2 3 4 5 6 7
/*****************************************************************************
 * Upnp_intell.cpp :  UPnP discovery module (Intel SDK)
 *****************************************************************************
 * Copyright (C) 2004-2006 the VideoLAN team
 * $Id$
 *
 * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
8 9
 *          Christian Henz <henz # c-lab.de>
 *
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*
  \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
  \TODO: Change names to VLC standard ???
30
  \TODO: Rewrite this using the new service discovery API (see sap.c, shout.c).
31 32 33 34 35 36 37 38 39 40
*/


#include <vector>
#include <string>

#include <upnp/upnp.h>
#include <upnp/upnptools.h>

#undef PACKAGE_NAME
41 42 43 44
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

45
#include <vlc/vlc.h>
Clément Stenac's avatar
Clément Stenac committed
46
#include <vlc_playlist.h>
47

48

49 50 51 52 53 54 55 56 57
// VLC handle

struct services_discovery_sys_t
{
    playlist_item_t *p_node_cat;
    playlist_item_t *p_node_one;
};


58 59 60 61 62 63
// Constants

const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";


64
// Classes
65 66 67 68 69 70 71 72

class MediaServer;
class MediaServerList;
class Item;
class Container;

// Cookie that is passed to the callback

73
typedef struct
74 75 76
{
    services_discovery_t* serviceDiscovery;
    UpnpClient_Handle clientHandle;
77
    MediaServerList* serverList;
78 79 80 81 82
} Cookie;


// Class definitions...

83 84
class Lockable
{
85 86
public:

87
    Lockable( Cookie* c )
88
    {
89
    vlc_mutex_init( &_mutex );
90 91
    }

92
    ~Lockable()
93
    {
94
    vlc_mutex_destroy( &_mutex );
95 96 97 98 99 100
    }

    void lock() { vlc_mutex_lock( &_mutex ); }
    void unlock() { vlc_mutex_unlock( &_mutex ); }

private:
101

102 103 104 105
    vlc_mutex_t _mutex;
};


106
class Locker
107 108
{
public:
109
    Locker( Lockable* l )
110
    {
111 112
    _lockable = l;
    _lockable->lock();
113 114
    }

115
    ~Locker()
116
    {
117
    _lockable->unlock();
118 119 120 121 122 123 124 125 126 127 128 129
    }

private:
    Lockable* _lockable;
};


class MediaServer
{
public:

    static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie );
130

131 132
    MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
    ~MediaServer();
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    const char* getUDN() const;
    const char* getFriendlyName() const;

    void setContentDirectoryEventURL( const char* url );
    const char* getContentDirectoryEventURL() const;

    void setContentDirectoryControlURL( const char* url );
    const char* getContentDirectoryControlURL() const;

    void subscribeToContentDirectory();
    void fetchContents();

    void setPlaylistNode( playlist_item_t* node );

    bool compareSID( const char* sid );

private:

    bool _fetchContents( Container* parent );
    void _buildPlaylist( Container* container );
    IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* );

    Cookie* _cookie;
157

158 159 160 161 162
    Container* _contents;
    playlist_item_t* _playlistNode;

    std::string _UDN;
    std::string _friendlyName;
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    std::string _contentDirectoryEventURL;
    std::string _contentDirectoryControlURL;

    int _subscriptionTimeOut;
    Upnp_SID _subscriptionID;
};


class MediaServerList
{
public:

    MediaServerList( Cookie* cookie );
    ~MediaServerList();

    bool addServer( MediaServer* s );
    void removeServer( const char* UDN );

    MediaServer* getServer( const char* UDN );
    MediaServer* getServerBySID( const char* );

private:

    Cookie* _cookie;

    std::vector<MediaServer*> _list;
};


193
class Item
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
{
public:

    Item( Container* parent, const char* objectID, const char* title, const char* resource );

    const char* getObjectID() const;
    const char* getTitle() const;
    const char* getResource() const;

    void setPlaylistNode( playlist_item_t* node );
    playlist_item_t* getPlaylistNode() const ;

private:

    playlist_item_t* _playlistNode;
209

210 211 212 213 214 215 216
    Container* _parent;
    std::string _objectID;
    std::string _title;
    std::string _resource;
};


217
class Container
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
{
public:

    Container( Container* parent, const char* objectID, const char* title );
    ~Container();

    void addItem( Item* item );
    void addContainer( Container* container );

    const char* getObjectID() const;
    const char* getTitle() const;

    unsigned int getNumItems() const;
    unsigned int getNumContainers() const;

    Item* getItem( unsigned int i ) const;
    Container* getContainer( unsigned int i ) const;

    void setPlaylistNode( playlist_item_t* node );
    playlist_item_t* getPlaylistNode() const;

private:

    playlist_item_t* _playlistNode;

    Container* _parent;

    std::string _objectID;
    std::string _title;
    std::vector<Item*> _items;
    std::vector<Container*> _containers;
};


// VLC callback prototypes

static int Open( vlc_object_t* );
static void Close( vlc_object_t* );
static void Run( services_discovery_t *p_sd );

// Module descriptor

vlc_module_begin();
set_shortname( "UPnP" );
set_description( _( "Universal Plug'n'Play discovery ( Intel SDK )" ) );
set_category( CAT_PLAYLIST );
set_subcategory( SUBCAT_PLAYLIST_SD );
set_capability( "services_discovery", 0 );
set_callbacks( Open, Close );
vlc_module_end();


// More prototypes...

static Lockable* CallbackLock;
static int Callback( Upnp_EventType eventType, void* event, void* pCookie );

const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName );
IXML_Document* parseBrowseResult( IXML_Document* doc );


// VLC callbacks...

static int Open( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
284 285 286
    services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
    malloc( sizeof( services_discovery_sys_t ) );

287
    p_sd->pf_run = Run;
288
    p_sd->p_sys = p_sys;
289 290

    /* Create our playlist node */
291 292
    playlist_NodesPairCreate( pl_Get( p_sd ), _("Devices"),
                              &p_sys->p_node_cat, &p_sys->p_node_one,
293
                              true );
294 295 296 297 298 299

    return VLC_SUCCESS;
}

static void Close( vlc_object_t *p_this )
{
300 301 302
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t *p_sys = p_sd->p_sys;

303 304 305 306
    playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_one, true,
                         true );
    playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, true,
                         true );
307 308

    free( p_sys );
309 310 311 312 313
}

static void Run( services_discovery_t* p_sd )
{
    int res;
314

315
    res = UpnpInit( 0, 0 );
316
    if( res != UPNP_E_SUCCESS )
317
    {
318 319
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
        return;
320 321 322 323 324 325 326 327 328
    }

    Cookie cookie;
    cookie.serviceDiscovery = p_sd;
    cookie.serverList = new MediaServerList( &cookie );

    CallbackLock = new Lockable( &cookie );

    res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
329
    if( res != UPNP_E_SUCCESS )
330
    {
331 332
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
        goto shutDown;
333 334 335
    }

    res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
336
    if( res != UPNP_E_SUCCESS )
337
    {
338 339
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
        goto shutDown;
340
    }
341

342
    msg_Dbg( p_sd, "UPnP discovery started" );
343
    while( !p_sd->b_die )
344
    {
345
        msleep( 500 );
346 347 348 349 350 351 352 353 354 355 356 357 358 359
    }

    msg_Dbg( p_sd, "UPnP discovery stopped" );

 shutDown:
    UpnpFinish();
    delete cookie.serverList;
    delete CallbackLock;
}


// XML utility functions:

// Returns the value of a child element, or 0 on error
360
const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
{
    if ( !parent ) return 0;
    if ( !tagName ) return 0;

    char* s = strdup( tagName );
    IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
    free( s );
    if ( !nodeList ) return 0;

    IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
    ixmlNodeList_free( nodeList );
    if ( !element ) return 0;

    IXML_Node* textNode = ixmlNode_getFirstChild( element );
    if ( !textNode ) return 0;

    return ixmlNode_getNodeValue( textNode );
}

// Extracts the result document from a SOAP response
381
IXML_Document* parseBrowseResult( IXML_Document* doc )
382 383
{
    if ( !doc ) return 0;
384

385 386
    IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
    if ( !resultList ) return 0;
387

388 389 390 391 392 393 394 395 396 397
    IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );

    ixmlNodeList_free( resultList );

    if ( !resultNode ) return 0;

    IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
    if ( !textNode ) return 0;

    const char* resultString = ixmlNode_getNodeValue( textNode );
398
    char* resultXML = strdup( resultString );
399

400 401 402 403 404 405 406 407 408
    IXML_Document* browseDoc = ixmlParseBuffer( resultXML );

    free( resultXML );

    return browseDoc;
}


// Handles all UPnP events
409
static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
410 411 412 413 414 415
{
    Locker locker( CallbackLock );

    Cookie* cookie = ( Cookie* )pCookie;

    switch( eventType ) {
416

417 418
    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
    case UPNP_DISCOVERY_SEARCH_RESULT:
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
    {
        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;

        IXML_Document *descriptionDoc = 0;

        int res;
        res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
        if ( res != UPNP_E_SUCCESS )
        {
          msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
          return res;
        }

        MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );

        ixmlDocument_free( descriptionDoc );
    }
    break;

438
    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
439 440
    {
        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
441

442 443 444
        cookie->serverList->removeServer( discovery->DeviceId );
    }
    break;
445 446

    case UPNP_EVENT_RECEIVED:
447 448
    {
        Upnp_Event* e = ( Upnp_Event* )event;
449

450 451 452 453
        MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
        if ( server ) server->fetchContents();
    }
    break;
454 455 456

    case UPNP_EVENT_AUTORENEWAL_FAILED:
    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
457 458
    {
        // Re-subscribe...
459

460 461 462 463 464 465
        Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;

        MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
        if ( server ) server->subscribeToContentDirectory();
    }
    break;
466

467 468 469
    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
        msg_Warn( cookie->serviceDiscovery, "subscription complete" );
        break;
470
 
471 472 473
    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
        msg_Warn( cookie->serviceDiscovery, "search timeout" );
        break;
474
 
475
    default:
476 477
    msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
    break;
478 479 480 481 482 483 484 485 486 487
    }

    return UPNP_E_SUCCESS;
}


// Class implementations...

// MediaServer...

488
void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
489 490 491
{
    if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
    if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
492

493 494 495 496 497
    const char* baseURL = location;

    // Try to extract baseURL

    IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
498 499 500 501 502 503 504 505 506
    if ( urlList )
    {
    if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
    {
        IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
        if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
    }

    ixmlNodeList_free( urlList );
507
    }
508

509 510 511 512 513
    // Get devices

    IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
    if ( deviceList )
    {
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

    for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
    {
        IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );

        const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
        if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
        if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;

        const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
        if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
        if ( cookie->serverList->getServer( UDN ) != 0 ) continue;

        const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
        if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }

        MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
        if ( !cookie->serverList->addServer( server ) ) {

        delete server;
        server = 0;
        continue;
        }

        // Check for ContentDirectory service...

        IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
        if ( serviceList )
        {
            for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
        {
            IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );

            const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
            if ( !serviceType ) continue;
            if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;

            const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
            if ( !eventSubURL ) continue;

            const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
            if ( !controlURL ) continue;

            // Try to subscribe to ContentDirectory service

            char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
            if ( url )
            {
                char* s1 = strdup( baseURL );
                char* s2 = strdup( eventSubURL );

                if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
                {
                // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );

                server->setContentDirectoryEventURL( url );
                server->subscribeToContentDirectory();
                }

                free( s1 );
                free( s2 );
                free( url );
            }

            // Try to browse content directory...

            url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
            if ( url )
            {
            char* s1 = strdup( baseURL );
            char* s2 = strdup( controlURL );

            if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
            {
                // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );

                server->setContentDirectoryControlURL( url );
                server->fetchContents();
            }

            free( s1 );
            free( s2 );
            free( url );
            }
        }

        ixmlNodeList_free( serviceList );
        }
    }

    ixmlNodeList_free( deviceList );
605 606 607
    }
}

608
MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
609 610 611 612 613 614 615 616 617 618
{
    _cookie = cookie;

    _UDN = UDN;
    _friendlyName = friendlyName;

    _contents = 0;
    _playlistNode = 0;
}

619 620 621
MediaServer::~MediaServer()
{
    if ( _contents )
622
    {
623
        playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) ,
624
                             _playlistNode, true, true );
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
    }

    delete _contents;
}

const char* MediaServer::getUDN() const
{
  const char* s = _UDN.c_str();
  return s;
}

const char* MediaServer::getFriendlyName() const
{
    const char* s = _friendlyName.c_str();
    return s;
}

642
void MediaServer::setContentDirectoryEventURL( const char* url )
643 644 645 646 647 648 649 650 651 652
{
    _contentDirectoryEventURL = url;
}

const char* MediaServer::getContentDirectoryEventURL() const
{
    const char* s =  _contentDirectoryEventURL.c_str();
    return s;
}

653
void MediaServer::setContentDirectoryControlURL( const char* url )
654 655 656 657 658 659 660 661 662
{
    _contentDirectoryControlURL = url;
}

const char* MediaServer::getContentDirectoryControlURL() const
{
    return _contentDirectoryControlURL.c_str();
}

663
void MediaServer::subscribeToContentDirectory()
664 665
{
    const char* url = getContentDirectoryEventURL();
666 667 668 669
    if ( !url || strcmp( url, "" ) == 0 )
    {
    msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
    return;
670 671 672 673
    }

    int timeOut = 1810;
    Upnp_SID sid;
674

675 676
    int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );

677
    if ( res == UPNP_E_SUCCESS )
678
    {
679 680 681 682
    _subscriptionTimeOut = timeOut;
    memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
    }
    else
683
    {
684
    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
685 686 687
    }
}

688 689
IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
                       const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
{
    IXML_Document* action = 0;
    IXML_Document* response = 0;

    const char* url = getContentDirectoryControlURL();
    if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return 0; }

    char* ObjectID = strdup( pObjectID );
    char* BrowseFlag = strdup( pBrowseFlag );
    char* Filter = strdup( pFilter );
    char* StartingIndex = strdup( pStartingIndex );
    char* RequestedCount = strdup( pRequestedCount );
    char* SortCriteria = strdup( pSortCriteria );

    char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );

    int res;

    res = UpnpAddToAction( &action, "Browse", serviceType, "ObjectID", ObjectID );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }

    res = UpnpAddToAction( &action, "Browse", serviceType, "BrowseFlag", BrowseFlag );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }

    res = UpnpAddToAction( &action, "Browse", serviceType, "Filter", Filter );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }

    res = UpnpAddToAction( &action, "Browse", serviceType, "StartingIndex", StartingIndex );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }

    res = UpnpAddToAction( &action, "Browse", serviceType, "RequestedCount", RequestedCount );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }

    res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria );
    if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
725 726 727 728 729 730 731 732 733 734 735 736

    res = UpnpSendAction( _cookie->clientHandle,
              url,
              CONTENT_DIRECTORY_SERVICE_TYPE,
              0,
              action,
              &response );
    if ( res != UPNP_E_SUCCESS )
    {
    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
    ixmlDocument_free( response );
    response = 0;
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
    }

 browseActionCleanup:

    free( ObjectID );
    free( BrowseFlag );
    free( Filter );
    free( StartingIndex );
    free( RequestedCount );
    free( SortCriteria );

    free( serviceType );

    ixmlDocument_free( action );
    return response;
}

754
void MediaServer::fetchContents()
755 756
{
    Container* root = new Container( 0, "0", getFriendlyName() );
757
    playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery );
758 759
    _fetchContents( root );

760
    if ( _contents )
761
    {
762
        PL_LOCK;
763
        playlist_NodeEmpty( p_playlist, _playlistNode, true );
764 765
        PL_UNLOCK;
        delete _contents;
766 767 768 769 770 771 772 773
    }

    _contents = root;
    _contents->setPlaylistNode( _playlistNode );

    _buildPlaylist( _contents );
}

774
bool MediaServer::_fetchContents( Container* parent )
775 776 777 778 779 780 781 782 783 784 785
{
    if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }

    IXML_Document* response = _browseAction( parent->getObjectID(), "BrowseDirectChildren", "*", "0", "0", "" );
    if ( !response ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }

    IXML_Document* result = parseBrowseResult( response );
    ixmlDocument_free( response );
    if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }

    IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
786
    if ( containerNodeList )
787
    {
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
    for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
    {
              IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );

        const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
        if ( !objectID ) continue;

        const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
        if ( !childCountStr ) continue;
        int childCount = atoi( childCountStr );

        const char* title = xml_getChildElementValue( containerElement, "dc:title" );
        if ( !title ) continue;

        const char* resource = xml_getChildElementValue( containerElement, "res" );

        if ( resource && childCount < 1 )
        {
        Item* item = new Item( parent, objectID, title, resource );
        parent->addItem( item );
        }
        else
        {
        Container* container = new Container( parent, objectID, title );
        parent->addContainer( container );

        if ( childCount > 0 ) _fetchContents( container );
        }
    }

    ixmlNodeList_free( containerNodeList );
819 820 821
    }

    IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
822 823 824
    if ( itemNodeList )
    {
        for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
825
    {
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
        IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );

        const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
        if ( !objectID ) continue;

        const char* title = xml_getChildElementValue( itemElement, "dc:title" );
        if ( !title ) continue;

        const char* resource = xml_getChildElementValue( itemElement, "res" );
        if ( !resource ) continue;

        Item* item = new Item( parent, objectID, title, resource );
        parent->addItem( item );
    }

    ixmlNodeList_free( itemNodeList );
842
    }
843

844
    ixmlDocument_free( result );
845

846 847 848
    return true;
}

849 850
void MediaServer::_buildPlaylist( Container* parent )
{
851
    playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery );
852
    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
853
    {
854 855
        Container* container = parent->getContainer( i );
        playlist_item_t* parentNode = parent->getPlaylistNode();
856

857
        char* title = strdup( container->getTitle() );
858
        playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL );
859
        free( title );
860

861 862
        container->setPlaylistNode( node );
        _buildPlaylist( container );
863 864
    }

865
    for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
866
    {
867 868
        Item* item = parent->getItem( i );
        playlist_item_t* parentNode = parent->getPlaylistNode();
869

870 871 872 873
        input_item_t* p_input = input_ItemNew( _cookie->serviceDiscovery,
                                               item->getResource(),
                                               item->getTitle() );
        int i_cat;
874
        /* FIXME: playlist_AddInput() can fail */
875
        playlist_BothAddInput( p_playlist, p_input, parentNode,
876
                               PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
877
                               false );
Rafaël Carré's avatar
Rafaël Carré committed
878
        vlc_gc_decref( p_input );
879
        /* TODO: do this better by storing ids */
880
        playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, false );
881 882
        assert( p_node );
        item->setPlaylistNode( p_node );
883 884 885
    }
}

886
void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
887 888 889 890
{
    _playlistNode = playlistNode;
}

891
bool MediaServer::compareSID( const char* sid )
892 893 894 895 896 897 898
{
    return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
}


// MediaServerList...

899
MediaServerList::MediaServerList( Cookie* cookie )
900 901 902 903
{
    _cookie = cookie;
}

904
MediaServerList::~MediaServerList()
905 906 907
{
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
908
    delete _list[i];
909 910 911
    }
}

912
bool MediaServerList::addServer( MediaServer* s )
913 914 915 916 917 918 919 920
{
    if ( getServer( s->getUDN() ) != 0 ) return false;

    msg_Dbg( _cookie->serviceDiscovery, "Adding server '%s'", s->getFriendlyName() );

    _list.push_back( s );

    char* name = strdup( s->getFriendlyName() );
921 922
    playlist_item_t* node = playlist_NodeCreate( pl_Get( _cookie->serviceDiscovery ),
                                                 name,
923
                                          _cookie->serviceDiscovery->p_sys->p_node_cat, 0, NULL );
924 925 926 927 928 929
    free( name );
    s->setPlaylistNode( node );

    return true;
}

930
MediaServer* MediaServerList::getServer( const char* UDN )
931 932
{
    MediaServer* result = 0;
933 934 935 936

    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
        if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
937
    {
938 939 940
        result = _list[i];
        break;
    }
941 942 943 944 945
    }

    return result;
}

946 947
MediaServer* MediaServerList::getServerBySID( const char* sid )
{
948 949
    MediaServer* server = 0;

950 951 952
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
    if ( _list[i]->compareSID( sid ) )
953
    {
954 955
        server = _list[i];
        break;
956
    }
957 958
    }

959 960 961
    return server;
}

962
void MediaServerList::removeServer( const char* UDN )
963 964 965 966
{
    MediaServer* server = getServer( UDN );
    if ( !server ) return;

967
    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
968 969

    std::vector<MediaServer*>::iterator it;
970
    for ( it = _list.begin(); it != _list.end(); it++ )
971
    {
972 973 974 975 976 977
        if ( *it == server )
    {
              _list.erase( it );
        delete server;
        break;
    }
978 979 980 981 982 983
    }
}


// Item...

984
Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
985 986
{
    _parent = parent;
987

988 989 990 991 992 993 994
    _objectID = objectID;
    _title = title;
    _resource = resource;

    _playlistNode = 0;
}

995
const char* Item::getObjectID() const
996 997 998 999
{
    return _objectID.c_str();
}

1000
const char* Item::getTitle() const
1001 1002 1003 1004
{
    return _title.c_str();
}

1005
const char* Item::getResource() const
1006 1007 1008 1009
{
    return _resource.c_str();
}

1010 1011 1012
void Item::setPlaylistNode( playlist_item_t* node )
{
    _playlistNode = node;
1013 1014
}

1015 1016 1017
playlist_item_t* Item::getPlaylistNode() const
{
    return _playlistNode;
1018 1019 1020 1021 1022
}


// Container...

1023
Container::Container( Container* parent, const char* objectID, const char* title )
1024 1025
{
    _parent = parent;
1026

1027 1028 1029 1030 1031 1032
    _objectID = objectID;
    _title = title;

    _playlistNode = 0;
}

1033
Container::~Container()
1034
{
1035
    for ( unsigned int i = 0; i < _containers.size(); i++ )
1036
    {
1037
    delete _containers[i];
1038 1039
    }

1040
    for ( unsigned int i = 0; i < _items.size(); i++ )
1041
    {
1042
    delete _items[i];
1043 1044 1045
    }
}

1046
void Container::addItem( Item* item )
1047 1048 1049 1050
{
    _items.push_back( item );
}

1051
void Container::addContainer( Container* container )
1052 1053 1054 1055
{
    _containers.push_back( container );
}

1056 1057 1058
const char* Container::getObjectID() const
{
    return _objectID.c_str();
1059 1060
}

1061 1062 1063
const char* Container::getTitle() const
{
    return _title.c_str();
1064 1065
}

1066 1067 1068
unsigned int Container::getNumItems() const
{
    return _items.size();
1069 1070
}

1071 1072 1073
unsigned int Container::getNumContainers() const
{
    return _containers.size();
1074 1075
}

1076
Item* Container::getItem( unsigned int i ) const
1077 1078 1079 1080 1081
{
    if ( i < _items.size() ) return _items[i];
    return 0;
}

1082
Container* Container::getContainer( unsigned int i ) const
1083 1084 1085 1086 1087
{
    if ( i < _containers.size() ) return _containers[i];
    return 0;
}

1088 1089 1090
void Container::setPlaylistNode( playlist_item_t* node )
{
    _playlistNode = node;
1091 1092
}

1093 1094 1095
playlist_item_t* Container::getPlaylistNode() const
{
    return _playlistNode;
1096
}