upnp_intel.cpp 29.5 KB
Newer Older
1
/*****************************************************************************
Rémi Duraffort's avatar
Rémi Duraffort committed
2
 * Upnp_intel.cpp :  UPnP discovery module (Intel SDK)
3
 *****************************************************************************
Rémi Duraffort's avatar
Rémi Duraffort committed
4
 * Copyright (C) 2004-2008 the VideoLAN team
5 6 7
 * $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_common.h>
46
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
47
#include <vlc_playlist.h>
48

Laurent Aimar's avatar
Laurent Aimar committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int Open( vlc_object_t* );
static void Close( vlc_object_t* );

vlc_module_begin();
    set_shortname( "UPnP" );
    set_description( N_( "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();


/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
68 69 70 71 72 73 74

// 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";


75
// Classes
76 77 78 79 80

class MediaServer;
class MediaServerList;
class Item;
class Container;
81
class Lockable;
82 83 84

// Cookie that is passed to the callback

85
typedef struct
86 87 88
{
    services_discovery_t* serviceDiscovery;
    UpnpClient_Handle clientHandle;
89
    MediaServerList* serverList;
90
    Lockable* lock;
91 92 93 94 95
} Cookie;


// Class definitions...

96 97
class Lockable
{
98
public:
99 100 101 102
    Lockable()
    {
        vlc_mutex_init( &_mutex );
    }
103

104
    ~Lockable()
105
    {
Laurent Aimar's avatar
Laurent Aimar committed
106
        vlc_mutex_destroy( &_mutex );
107 108 109 110 111 112 113 114 115 116
    }

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

private:
    vlc_mutex_t _mutex;
};


117
class Locker
118 119
{
public:
120
    Locker( Lockable* l )
121
    {
122 123
    _lockable = l;
    _lockable->lock();
124 125
    }

126
    ~Locker()
127
    {
128
    _lockable->unlock();
129 130 131 132 133 134 135 136 137 138 139 140
    }

private:
    Lockable* _lockable;
};


class MediaServer
{
public:

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

142 143
    MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
    ~MediaServer();
144

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    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;
168

169 170 171 172 173
    Container* _contents;
    playlist_item_t* _playlistNode;

    std::string _UDN;
    std::string _friendlyName;
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    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;
};


204
class Item
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
{
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;
220

221 222 223 224 225 226 227
    Container* _parent;
    std::string _objectID;
    std::string _title;
    std::string _resource;
};


228
class Container
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
{
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;
};


263 264 265 266 267 268 269 270 271 272 273 274 275

// VLC handle

struct services_discovery_sys_t
{
    playlist_t *p_playlist;
    playlist_item_t *p_node_cat;
    playlist_item_t *p_node_one;
    Cookie cookie;
};



276 277
// VLC callback prototypes

278
static playlist_t *pl_Get( services_discovery_t *p_sd )
279 280 281
{
    return p_sd->p_sys->p_playlist;
}
282

283 284


285 286 287 288 289 290 291 292
// More prototypes...

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 );


293

294 295 296 297 298
// VLC callbacks...

static int Open( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
299
    services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
300 301
        malloc( sizeof( services_discovery_sys_t ) );
    playlist_t *p_playlist = pl_Yield( p_sd );
302 303

    p_sd->p_sys = p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
304
    p_sys->p_playlist = p_playlist;
305
    Cookie *cookie = &p_sys->cookie;
306 307

    /* Create our playlist node */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
308 309
    PL_LOCK;
    playlist_NodesPairCreate( p_playlist, _("Devices"),
310
                              &p_sys->p_node_cat, &p_sys->p_node_one,
311
                              true );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
312
    PL_UNLOCK;
313

314 315 316
    cookie->serviceDiscovery = p_sd;
    cookie->lock = new Lockable();
    cookie->serverList = new MediaServerList( cookie );
317

318
    int res = UpnpInit( 0, 0 );
319
    if( res != UPNP_E_SUCCESS )
320
    {
321
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
322
        goto shutDown;
323 324
    }

325
    res = UpnpRegisterClient( Callback, cookie, &cookie->clientHandle );
326
    if( res != UPNP_E_SUCCESS )
327
    {
328 329
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
        goto shutDown;
330 331
    }

332 333
    res = UpnpSearchAsync( cookie->clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE,
                           cookie );
334
    if( res != UPNP_E_SUCCESS )
335
    {
336 337
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
        goto shutDown;
338
    }
339

340
    return VLC_SUCCESS;
341 342

 shutDown:
343 344
    Close( p_this );
    return VLC_EGENERIC;
345 346
}

347 348 349 350
static void Close( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t *p_sys = p_sd->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
351
    playlist_t *p_playlist = p_sys->p_playlist;
352 353 354 355 356

    UpnpFinish();
    delete p_sys->cookie.serverList;
    delete p_sys->cookie.lock;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
357 358 359 360
    PL_LOCK;
    playlist_NodeDelete( p_playlist, p_sys->p_node_one, true, true );
    playlist_NodeDelete( p_playlist, p_sys->p_node_cat, true, true );
    PL_UNLOCK;
361 362 363
    pl_Release( p_sd );
    free( p_sys );
}
364 365 366 367

// XML utility functions:

// Returns the value of a child element, or 0 on error
368
const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
{
    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
389
IXML_Document* parseBrowseResult( IXML_Document* doc )
390 391
{
    if ( !doc ) return 0;
392

393 394
    IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
    if ( !resultList ) return 0;
395

396 397 398 399 400 401 402 403 404 405
    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 );
406
    char* resultXML = strdup( resultString );
407

408 409 410 411 412 413 414 415 416
    IXML_Document* browseDoc = ixmlParseBuffer( resultXML );

    free( resultXML );

    return browseDoc;
}


// Handles all UPnP events
417
static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
418 419 420
{
    Cookie* cookie = ( Cookie* )pCookie;

421 422
    Locker locker( cookie->lock );

423
    switch( eventType ) {
424

425 426
    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
    case UPNP_DISCOVERY_SEARCH_RESULT:
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    {
        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;

446
    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
447 448
    {
        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
449

450 451 452
        cookie->serverList->removeServer( discovery->DeviceId );
    }
    break;
453 454

    case UPNP_EVENT_RECEIVED:
455 456
    {
        Upnp_Event* e = ( Upnp_Event* )event;
457

458 459 460 461
        MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
        if ( server ) server->fetchContents();
    }
    break;
462 463 464

    case UPNP_EVENT_AUTORENEWAL_FAILED:
    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
465 466
    {
        // Re-subscribe...
467

468 469 470 471 472 473
        Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;

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

475 476 477
    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
        msg_Warn( cookie->serviceDiscovery, "subscription complete" );
        break;
478
 
479 480 481
    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
        msg_Warn( cookie->serviceDiscovery, "search timeout" );
        break;
482
 
483
    default:
484 485
    msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
    break;
486 487 488 489 490 491 492 493 494 495
    }

    return UPNP_E_SUCCESS;
}


// Class implementations...

// MediaServer...

496
void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
497 498 499
{
    if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
    if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
500

501 502 503 504 505
    const char* baseURL = location;

    // Try to extract baseURL

    IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
506 507 508 509 510 511 512 513 514
    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 );
515
    }
516

517 518 519 520 521
    // Get devices

    IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
    if ( deviceList )
    {
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 605 606 607 608 609 610 611 612

    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 );
613 614 615
    }
}

616
MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
617 618 619 620 621 622 623 624 625 626
{
    _cookie = cookie;

    _UDN = UDN;
    _friendlyName = friendlyName;

    _contents = 0;
    _playlistNode = 0;
}

627 628 629
MediaServer::~MediaServer()
{
    if ( _contents )
630
    {
631
        vlc_object_lock( _cookie->serviceDiscovery->p_sys->p_playlist );
632
        playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) ,
633
                             _playlistNode, true, true );
634
        vlc_object_unlock( _cookie->serviceDiscovery->p_sys->p_playlist );
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
    }

    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;
}

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

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

663
void MediaServer::setContentDirectoryControlURL( const char* url )
664 665 666 667 668 669 670 671 672
{
    _contentDirectoryControlURL = url;
}

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

673
void MediaServer::subscribeToContentDirectory()
674 675
{
    const char* url = getContentDirectoryEventURL();
676 677 678 679
    if ( !url || strcmp( url, "" ) == 0 )
    {
    msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
    return;
680 681 682 683
    }

    int timeOut = 1810;
    Upnp_SID sid;
684

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

687
    if ( res == UPNP_E_SUCCESS )
688
    {
689 690 691 692
    _subscriptionTimeOut = timeOut;
    memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
    }
    else
693
    {
694
    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
695 696 697
    }
}

698 699
IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
                       const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
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 725 726 727 728 729 730 731 732 733 734
{
    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; }
735 736 737 738 739 740 741 742 743 744 745 746

    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;
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
    }

 browseActionCleanup:

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

    free( serviceType );

    ixmlDocument_free( action );
    return response;
}

764
void MediaServer::fetchContents()
765 766
{
    Container* root = new Container( 0, "0", getFriendlyName() );
767
    playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery );
768 769
    _fetchContents( root );

770
    if ( _contents )
771
    {
772
        PL_LOCK;
773
        playlist_NodeEmpty( p_playlist, _playlistNode, true );
774 775
        PL_UNLOCK;
        delete _contents;
776 777 778 779 780 781 782 783
    }

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

    _buildPlaylist( _contents );
}

784
bool MediaServer::_fetchContents( Container* parent )
785 786 787 788 789 790 791 792 793 794 795
{
    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" );
796
    if ( containerNodeList )
797
    {
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
    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 );
829 830 831
    }

    IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
832 833 834
    if ( itemNodeList )
    {
        for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
835
    {
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
        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 );
852
    }
853

854
    ixmlDocument_free( result );
855

856 857 858
    return true;
}

859 860
void MediaServer::_buildPlaylist( Container* parent )
{
861
    playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery );
862
    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
863
    {
864 865
        Container* container = parent->getContainer( i );
        playlist_item_t* parentNode = parent->getPlaylistNode();
866

867
        char* title = strdup( container->getTitle() );
868
        playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL );
869
        free( title );
870

871 872
        container->setPlaylistNode( node );
        _buildPlaylist( container );
873 874
    }

875
    for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
876
    {
877 878
        Item* item = parent->getItem( i );
        playlist_item_t* parentNode = parent->getPlaylistNode();
879

880
        input_item_t* p_input = input_item_New( _cookie->serviceDiscovery,
881 882 883
                                               item->getResource(),
                                               item->getTitle() );
        int i_cat;
884
        /* FIXME: playlist_AddInput() can fail */
885
        playlist_BothAddInput( p_playlist, p_input, parentNode,
886
                               PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
887
                               pl_Unlocked );
Rafaël Carré's avatar
Rafaël Carré committed
888
        vlc_gc_decref( p_input );
889
        /* TODO: do this better by storing ids */
890
        playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, false );
891 892
        assert( p_node );
        item->setPlaylistNode( p_node );
893 894 895
    }
}

896
void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
897 898 899 900
{
    _playlistNode = playlistNode;
}

901
bool MediaServer::compareSID( const char* sid )
902 903 904 905 906 907 908
{
    return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
}


// MediaServerList...

909
MediaServerList::MediaServerList( Cookie* cookie )
910 911 912 913
{
    _cookie = cookie;
}

914
MediaServerList::~MediaServerList()
915 916 917
{
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
918
    delete _list[i];
919 920 921
    }
}

922
bool MediaServerList::addServer( MediaServer* s )
923 924 925 926 927 928 929 930
{
    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() );
931 932 933 934 935 936
    vlc_object_lock( _cookie->serviceDiscovery->p_sys->p_playlist );
    playlist_item_t* node = playlist_NodeCreate(
                                pl_Get( _cookie->serviceDiscovery ), name,
                                _cookie->serviceDiscovery->p_sys->p_node_cat,
                                0, NULL );
    vlc_object_unlock( _cookie->serviceDiscovery->p_sys->p_playlist );
937 938 939 940 941 942
    free( name );
    s->setPlaylistNode( node );

    return true;
}

943
MediaServer* MediaServerList::getServer( const char* UDN )
944 945
{
    MediaServer* result = 0;
946 947 948 949

    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
        if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
950
    {
951 952 953
        result = _list[i];
        break;
    }
954 955 956 957 958
    }

    return result;
}

959 960
MediaServer* MediaServerList::getServerBySID( const char* sid )
{
961 962
    MediaServer* server = 0;

963 964 965
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
    if ( _list[i]->compareSID( sid ) )
966
    {
967 968
        server = _list[i];
        break;
969
    }
970 971
    }

972 973 974
    return server;
}

975
void MediaServerList::removeServer( const char* UDN )
976 977 978 979
{
    MediaServer* server = getServer( UDN );
    if ( !server ) return;

980
    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
981 982

    std::vector<MediaServer*>::iterator it;
983
    for ( it = _list.begin(); it != _list.end(); it++ )
984
    {
985 986 987 988 989 990
        if ( *it == server )
    {
              _list.erase( it );
        delete server;
        break;
    }
991 992 993 994 995 996
    }
}


// Item...

997
Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
998 999
{
    _parent = parent;
1000

1001 1002 1003 1004 1005 1006 1007
    _objectID = objectID;
    _title = title;
    _resource = resource;

    _playlistNode = 0;
}

1008
const char* Item::getObjectID() const
1009 1010 1011 1012
{
    return _objectID.c_str();
}

1013
const char* Item::getTitle() const
1014 1015 1016 1017
{
    return _title.c_str();
}

1018
const char* Item::getResource() const
1019 1020 1021 1022
{
    return _resource.c_str();
}

1023 1024 1025
void Item::setPlaylistNode( playlist_item_t* node )
{
    _playlistNode = node;
1026 1027
}

1028 1029 1030
playlist_item_t* Item::getPlaylistNode() const
{
    return _playlistNode;
1031 1032 1033 1034 1035
}


// Container...

1036
Container::Container( Container* parent, const char* objectID, const char* title )
1037 1038
{
    _parent = parent;
1039

1040 1041 1042 1043 1044 1045
    _objectID = objectID;
    _title = title;

    _playlistNode = 0;
}

1046
Container::~Container()
1047
{
1048
    for ( unsigned int i = 0; i < _containers.size(); i++ )
1049
    {
1050
    delete _containers[i];
1051 1052
    }

1053
    for ( unsigned int i = 0; i < _items.size(); i++ )
1054
    {
1055
    delete _items[i];
1056 1057 1058
    }
}

1059
void Container::addItem( Item* item )
1060 1061 1062 1063
{
    _items.push_back( item );
}

1064
void Container::addContainer( Container* container )
1065 1066 1067 1068
{
    _containers.push_back( container );
}

1069 1070 1071
const char* Container::getObjectID() const
{
    return _objectID.c_str();
1072 1073
}

1074 1075 1076
const char* Container::getTitle() const
{
    return _title.c_str();
1077 1078
}

1079 1080 1081
unsigned int Container::getNumItems() const
{
    return _items.size();
1082 1083
}

1084 1085 1086
unsigned int Container::getNumContainers() const
{
    return _containers.size();
1087 1088
}

1089
Item* Container::getItem( unsigned int i ) const
1090 1091 1092 1093 1094
{
    if ( i < _items.size() ) return _items[i];
    return 0;
}

1095
Container* Container::getContainer( unsigned int i ) const
1096 1097 1098 1099 1100
{
    if ( i < _containers.size() ) return _containers[i];
    return 0;
}

1101 1102 1103
void Container::setPlaylistNode( playlist_item_t* node )
{
    _playlistNode = node;
1104 1105
}

1106 1107 1108
playlist_item_t* Container::getPlaylistNode() const
{
    return _playlistNode;
1109
}