upnp_intel.cpp 27.9 KB
Newer Older
1
/*****************************************************************************
ivoire's avatar
ivoire committed
2
 * Upnp_intel.cpp :  UPnP discovery module (Intel SDK)
3
 *****************************************************************************
ivoire's avatar
ivoire committed
4
 * Copyright (C) 2004-2008 the VideoLAN team
5
6
7
 * $Id$
 *
 * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
8
 *          Christian Henz <henz # c-lab.de>
9
 *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
10
 *
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 * 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 ???
*/
#undef PACKAGE_NAME
33
34
35
36
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

37
38
#include "upnp_intel.hpp"

39
#include <vlc_plugin.h>
40
#include <vlc_services_discovery.h>
41

42

43
// Constants
44
45
46
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";

47
48
// VLC handle
struct services_discovery_sys_t
49
50
{
    UpnpClient_Handle clientHandle;
51
    MediaServerList* serverList;
ivoire's avatar
ivoire committed
52
    vlc_mutex_t callbackLock;
53
};
54
55

// VLC callback prototypes
56
57
static int Open( vlc_object_t* );
static void Close( vlc_object_t* );
58
VLC_SD_PROBE_HELPER("upnp_intel", "Universal Plug'n'Play", SD_CAT_LAN)
59
60

// Module descriptor
61

62
vlc_module_begin();
63
    set_shortname( "UPnP" );
64
    set_description( N_( "Universal Plug'n'Play" ) );
65
66
67
68
    set_category( CAT_PLAYLIST );
    set_subcategory( SUBCAT_PLAYLIST_SD );
    set_capability( "services_discovery", 0 );
    set_callbacks( Open, Close );
69
70

    VLC_SD_PROBE_SUBMODULE
71
vlc_module_end();
ivoire's avatar
ivoire committed
72
73


74
75
// More prototypes...

76
static int Callback( Upnp_EventType eventType, void* event, void* user_data );
77

78
79
const char* xml_getChildElementValue( IXML_Element* parent,
                                      const char*   tagName );
80

81
IXML_Document* parseBrowseResult( IXML_Document* doc );
82

83

84
85
86
87
// VLC callbacks...

static int Open( vlc_object_t *p_this )
{
88
    int res;
89
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
90
    services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
91
            calloc( 1, sizeof( services_discovery_sys_t ) );
92

93
94
    if(!(p_sd->p_sys = p_sys))
        return VLC_ENOMEM;
95

96
    res = UpnpInit( 0, 0 );
97
    if( res != UPNP_E_SUCCESS )
98
    {
99
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
ivoire's avatar
ivoire committed
100
        free( p_sys );
101
        return VLC_EGENERIC;
102
103
    }

104
    p_sys->serverList = new MediaServerList( p_sd );
ivoire's avatar
ivoire committed
105
    vlc_mutex_init( &p_sys->callbackLock );
106

107
    res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
108
    if( res != UPNP_E_SUCCESS )
109
    {
110
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
111
112
        Close( (vlc_object_t*) p_sd );
        return VLC_EGENERIC;
113
114
    }

115
116
    res = UpnpSearchAsync( p_sys->clientHandle, 5,
            MEDIA_SERVER_DEVICE_TYPE, p_sd );
ivoire's avatar
ivoire committed
117

118
    if( res != UPNP_E_SUCCESS )
119
    {
120
        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
121
122
        Close( (vlc_object_t*) p_sd );
        return VLC_EGENERIC;
123
    }
124

125
    return VLC_SUCCESS;
126
127
}

128
129
130
131
132
static void Close( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;

    UpnpFinish();
133
    delete p_sd->p_sys->serverList;
ivoire's avatar
ivoire committed
134
    vlc_mutex_destroy( &p_sd->p_sys->callbackLock );
135

136
    free( p_sd->p_sys );
137
}
138
139
140
141

// XML utility functions:

// Returns the value of a child element, or 0 on error
142
143
const char* xml_getChildElementValue( IXML_Element* parent,
                                      const char*   tagName )
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
{
    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
164
IXML_Document* parseBrowseResult( IXML_Document* doc )
165
{
166
167
    ixmlRelaxParser(1);

168
    if ( !doc ) return 0;
169

170
171
172
    IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
                                                                   "Result" );

173
    if ( !resultList ) return 0;
174

175
176
177
178
179
180
181
182
183
184
    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 );
185
    char* resultXML = strdup( resultString );
186

187
188
189
190
191
192
193
194
195
    IXML_Document* browseDoc = ixmlParseBuffer( resultXML );

    free( resultXML );

    return browseDoc;
}


// Handles all UPnP events
196
static int Callback( Upnp_EventType eventType, void* event, void* user_data )
197
{
198
199
    services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
    services_discovery_sys_t* p_sys = p_sd->p_sys;
ivoire's avatar
ivoire committed
200
    vlc_mutex_locker locker( &p_sys->callbackLock );
201

202
    switch( eventType ) {
203

204
205
    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
    case UPNP_DISCOVERY_SEARCH_RESULT:
206
207
208
209
210
211
212
213
214
    {
        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;

        IXML_Document *descriptionDoc = 0;

        int res;
        res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
        if ( res != UPNP_E_SUCCESS )
        {
215
216
217
218
            msg_Dbg( p_sd,
                    "%s:%d: Could not download device description!",
                    __FILE__, __LINE__ );
            return res;
219
220
        }

221
222
        MediaServer::parseDeviceDescription( descriptionDoc,
                discovery->Location, p_sd );
223
224
225
226
227

        ixmlDocument_free( descriptionDoc );
    }
    break;

228
    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
229
230
    {
        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
231

232
        p_sys->serverList->removeServer( discovery->DeviceId );
233
234
    }
    break;
235
236

    case UPNP_EVENT_RECEIVED:
237
238
    {
        Upnp_Event* e = ( Upnp_Event* )event;
239

240
        MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
241
242
243
        if ( server ) server->fetchContents();
    }
    break;
244
245
246

    case UPNP_EVENT_AUTORENEWAL_FAILED:
    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
247
248
    {
        // Re-subscribe...
249

250
251
        Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;

252
        MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
253
254
255
        if ( server ) server->subscribeToContentDirectory();
    }
    break;
256

257
    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
258
        msg_Warn( p_sd, "subscription complete" );
259
        break;
260

261
    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
262
        msg_Warn( p_sd, "search timeout" );
263
        break;
264

265
    default:
266
267
268
    msg_Dbg( p_sd,
            "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
            __FILE__, __LINE__, eventType );
269
    break;
270
271
272
273
274
275
276
277
278
279
    }

    return UPNP_E_SUCCESS;
}


// Class implementations...

// MediaServer...

280
281
282
void MediaServer::parseDeviceDescription( IXML_Document* doc,
                                          const char*    location,
                                          services_discovery_t* p_sd )
283
{
284
    if ( !doc )
285
286
    {
        msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
287
288
        return;
    }
289

290
291
    if ( !location )
    {
292
        msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
293
294
        return;
    }
295

296
297
298
299
300
    const char* baseURL = location;

    // Try to extract baseURL

    IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
301
    if ( !urlList )
302
303
    {

304
305
306
307
308
309
310
        if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
        {
            IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
            if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
        }

        ixmlNodeList_free( urlList );
311
    }
312

313
314
    // Get devices

315
316
    IXML_NodeList* deviceList =
                ixmlDocument_getElementsByTagName( doc, "device" );
317

318
319
    if ( deviceList )
    {
320
        for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
321
        {
322
323
            IXML_Element* deviceElement =
                   ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
324

325
326
327
            const char* deviceType = xml_getChildElementValue( deviceElement,
                                                               "deviceType" );
            if ( !deviceType )
328
            {
329
330
331
332
                msg_Dbg( p_sd,
                        "%s:%d: no deviceType!",
                        __FILE__, __LINE__ );
                continue;
333
334
            }

335
336
            if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
                continue;
337

338
339
            const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
            if ( !UDN )
340
            {
341
342
343
344
                msg_Dbg( p_sd, "%s:%d: no UDN!",
                        __FILE__, __LINE__ );
                continue;
            }
345

346
347
348
349
            if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
                continue;

            const char* friendlyName =
350
                       xml_getChildElementValue( deviceElement,
351
                                                 "friendlyName" );
352

353
            if ( !friendlyName )
354
            {
355
356
                msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
                continue;
357
358
            }

359
            MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
360

361
362
            if ( !p_sd->p_sys->serverList->addServer( server ) )
            {
363

364
365
366
367
                delete server;
                server = 0;
                continue;
            }
368

369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
            // 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 )
                        {
                            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 )
                        {
                            server->setContentDirectoryControlURL( url );
                            server->fetchContents();
                        }

                        free( s1 );
                        free( s2 );
                        free( url );
                    }
               }
               ixmlNodeList_free( serviceList );
           }
       }
       ixmlNodeList_free( deviceList );
449
450
451
    }
}

452
453
454
MediaServer::MediaServer( const char* UDN,
                          const char* friendlyName,
                          services_discovery_t* p_sd )
455
{
456
    _p_sd = p_sd;
457
458
459
460

    _UDN = UDN;
    _friendlyName = friendlyName;

461
462
    _contents = NULL;
    _inputItem = NULL;
463
464
}

465
466
MediaServer::~MediaServer()
{
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    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;
}

482
void MediaServer::setContentDirectoryEventURL( const char* url )
483
484
485
486
487
488
489
490
491
492
{
    _contentDirectoryEventURL = url;
}

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

493
void MediaServer::setContentDirectoryControlURL( const char* url )
494
495
496
497
498
499
500
501
502
{
    _contentDirectoryControlURL = url;
}

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

503
void MediaServer::subscribeToContentDirectory()
504
505
{
    const char* url = getContentDirectoryEventURL();
506
507
    if ( !url || strcmp( url, "" ) == 0 )
    {
508
509
        msg_Dbg( _p_sd, "No subscription url set!" );
        return;
510
511
512
513
    }

    int timeOut = 1810;
    Upnp_SID sid;
514

515
    int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
516

517
    if ( res == UPNP_E_SUCCESS )
518
    {
519
520
        _subscriptionTimeOut = timeOut;
        memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
521
522
    }
    else
523
    {
524
525
526
        msg_Dbg( _p_sd,
                "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
                getFriendlyName(), UpnpGetErrorMessage( res ) );
527
528
529
    }
}

530
531
532
533
534
535
IXML_Document* MediaServer::_browseAction( const char* pObjectID,
                                           const char* pBrowseFlag,
                                           const char* pFilter,
                                           const char* pStartingIndex,
                                           const char* pRequestedCount,
                                           const char* pSortCriteria )
536
537
538
539
{
    IXML_Document* action = 0;
    IXML_Document* response = 0;
    const char* url = getContentDirectoryControlURL();
540

541
542
543
544
545
    if ( !url || strcmp( url, "" ) == 0 )
    {
        msg_Dbg( _p_sd, "No subscription url set!" );
        return 0;
    }
546
547
548
549
550
551
552
553
554
555
556

    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;

557
558
    res = UpnpAddToAction( &action, "Browse",
            serviceType, "ObjectID", ObjectID );
559
560

    if ( res != UPNP_E_SUCCESS )
561
562
563
564
565
566
    {
        msg_Dbg( _p_sd,
                 "%s:%d: ERROR: %s", __FILE__, __LINE__,
                 UpnpGetErrorMessage( res ) );
        goto browseActionCleanup;
    }
567

568
569
    res = UpnpAddToAction( &action, "Browse",
            serviceType, "BrowseFlag", BrowseFlag );
570

571
572
573
574
575
576
577
    if ( res != UPNP_E_SUCCESS )
    {
        msg_Dbg( _p_sd,
             "%s:%d: ERROR: %s", __FILE__, __LINE__,
             UpnpGetErrorMessage( res ) );
        goto browseActionCleanup;
    }
578

579
580
    res = UpnpAddToAction( &action, "Browse",
            serviceType, "Filter", Filter );
581

582
583
584
585
586
587
588
    if ( res != UPNP_E_SUCCESS )
    {
        msg_Dbg( _p_sd,
             "%s:%d: ERROR: %s", __FILE__, __LINE__,
             UpnpGetErrorMessage( res ) );
        goto browseActionCleanup;
    }
589

590
591
    res = UpnpAddToAction( &action, "Browse",
            serviceType, "StartingIndex", StartingIndex );
592

593
594
595
596
597
598
599
600
601
602
    if ( res != UPNP_E_SUCCESS )
    {
        msg_Dbg( _p_sd,
             "%s:%d: ERROR: %s", __FILE__, __LINE__,
             UpnpGetErrorMessage( res ) );
        goto browseActionCleanup;
    }

    res = UpnpAddToAction( &action, "Browse",
            serviceType, "RequestedCount", RequestedCount );
603

604
605
606
607
608
    if ( res != UPNP_E_SUCCESS )
    {
        msg_Dbg( _p_sd,
                "%s:%d: ERROR: %s", __FILE__, __LINE__,
                UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
609

610
611
    res = UpnpAddToAction( &action, "Browse",
            serviceType, "SortCriteria", SortCriteria );
612

613
614
615
616
617
618
619
620
621
    if ( res != UPNP_E_SUCCESS )
    {
        msg_Dbg( _p_sd,
             "%s:%d: ERROR: %s", __FILE__, __LINE__,
             UpnpGetErrorMessage( res ) );
        goto browseActionCleanup;
    }

    res = UpnpSendAction( _p_sd->p_sys->clientHandle,
622
623
624
625
626
              url,
              CONTENT_DIRECTORY_SERVICE_TYPE,
              0,
              action,
              &response );
627

628
629
    if ( res != UPNP_E_SUCCESS )
    {
630
631
632
633
634
635
636
        msg_Dbg( _p_sd,
                "%s:%d: ERROR: %s when trying the send() action with URL: %s",
                __FILE__, __LINE__,
                UpnpGetErrorMessage( res ), url );

        ixmlDocument_free( response );
        response = 0;
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
    }

 browseActionCleanup:

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

    free( serviceType );

    ixmlDocument_free( action );
    return response;
}

654
void MediaServer::fetchContents()
655
656
657
658
659
{
    Container* root = new Container( 0, "0", getFriendlyName() );
    _fetchContents( root );

    _contents = root;
660
    _contents->setInputItem( _inputItem );
661

jpd's avatar
jpd committed
662
    _buildPlaylist( _contents, NULL );
663
664
}

665
bool MediaServer::_fetchContents( Container* parent )
666
{
667
668
669
670
671
672
    if (!parent)
    {
        msg_Dbg( _p_sd,
                "%s:%d: parent==NULL", __FILE__, __LINE__ );
        return false;
    }
673

674
675
676
677
678
679
680
681
682
683
    IXML_Document* response = _browseAction( parent->getObjectID(),
                                      "BrowseDirectChildren",
                                      "*", "0", "0", "" );
    if ( !response )
    {
        msg_Dbg( _p_sd,
                "%s:%d: ERROR! No response from browse() action",
                __FILE__, __LINE__ );
        return false;
    }
684
685
686

    IXML_Document* result = parseBrowseResult( response );
    ixmlDocument_free( response );
687

688
689
690
691
692
693
694
    if ( !result )
    {
        msg_Dbg( _p_sd,
                "%s:%d: ERROR! browse() response parsing failed",
                __FILE__, __LINE__ );
        return false;
    }
695

696
697
    IXML_NodeList* containerNodeList =
                ixmlDocument_getElementsByTagName( result, "container" );
698

699
    if ( containerNodeList )
700
    {
701
702
        for ( unsigned int i = 0;
                i < ixmlNodeList_length( containerNodeList ); i++ )
703
        {
704
705
706
707
708
709
710
711
712
713
            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" );
714

715
716
            if ( !childCountStr )
                continue;
717

718
719
720
            int childCount = atoi( childCountStr );
            const char* title = xml_getChildElementValue( containerElement,
                                                          "dc:title" );
721

722
723
            if ( !title )
                continue;
724

725
726
727
728
729
730
731
732
            const char* resource = xml_getChildElementValue( containerElement,
                                                             "res" );

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

734
735
736
737
            else
            {
                Container* container = new Container( parent, objectID, title );
                parent->addContainer( container );
738

739
740
741
742
743
                if ( childCount > 0 )
                    _fetchContents( container );
            }
        }
        ixmlNodeList_free( containerNodeList );
744
745
    }

746
747
    IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
                                                                     "item" );
748
749
750
    if ( itemNodeList )
    {
        for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
751
752
753
754
755
756
        {
            IXML_Element* itemElement =
                        ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );

            const char* objectID =
                        ixmlElement_getAttribute( itemElement, "id" );
757

758
759
760
761
762
            if ( !objectID )
                continue;

            const char* title =
                        xml_getChildElementValue( itemElement, "dc:title" );
763

764
765
766
767
768
            if ( !title )
                continue;

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

770
771
772
773
774
775
776
            if ( !resource )
                continue;

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

779
780
781
782
    ixmlDocument_free( result );
    return true;
}

jpd's avatar
jpd committed
783
void MediaServer::_buildPlaylist( Container* parent, input_item_node_t *p_input_node )
784
{
jpd's avatar
jpd committed
785
786
787
788
    bool send = p_input_node == NULL;
    if( send )
        p_input_node = input_item_node_Create( parent->getInputItem() );

789
    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
790
    {
791
        Container* container = parent->getContainer( i );
792

793
        input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", container->getTitle() );
jpd's avatar
jpd committed
794
795
        input_item_node_t *p_new_node =
            input_item_node_AppendItem( p_input_node, p_input_item );
796

797
        container->setInputItem( p_input_item );
jpd's avatar
jpd committed
798
        _buildPlaylist( container, p_new_node );
799
800
    }

801
    for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
802
    {
803
        Item* item = parent->getItem( i );
804

805
        input_item_t* p_input_item = input_item_New( _p_sd,
806
807
                                               item->getResource(),
                                               item->getTitle() );
808
        assert( p_input_item );
jpd's avatar
jpd committed
809
        input_item_node_AppendItem( p_input_node, p_input_item );
810
        item->setInputItem( p_input_item );
811
    }
jpd's avatar
jpd committed
812
813

    if( send )
814
        input_item_node_PostAndDelete( p_input_node );
815
816
}

817
void MediaServer::setInputItem( input_item_t* p_input_item )
818
{
819
820
821
822
823
824
825
826
    if(_inputItem == p_input_item)
        return;

    if(_inputItem)
        vlc_gc_decref( _inputItem );

    vlc_gc_incref( p_input_item );
    _inputItem = p_input_item;
827
828
}

829
bool MediaServer::compareSID( const char* sid )
830
831
832
833
834
835
836
{
    return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
}


// MediaServerList...

837
MediaServerList::MediaServerList( services_discovery_t* p_sd )
838
{
839
    _p_sd = p_sd;
840
841
}

842
MediaServerList::~MediaServerList()
843
844
845
{
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
846
        delete _list[i];
847
848
849
    }
}

850
bool MediaServerList::addServer( MediaServer* s )
851
{
852
    input_item_t* p_input_item = NULL;
853
854
    if ( getServer( s->getUDN() ) != 0 ) return false;

855
856
    msg_Dbg( _p_sd, "Adding server '%s'",
            s->getFriendlyName() );
857

858
859
    services_discovery_t* p_sd = _p_sd;

860
    p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() );
861
    s->setInputItem( p_input_item );
862

863
    services_discovery_AddItem( p_sd, p_input_item, NULL );
864
865

    _list.push_back( s );
866
867
868
869

    return true;
}

870
MediaServer* MediaServerList::getServer( const char* UDN )
871
872
{
    MediaServer* result = 0;
873
874
875
876

    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
        if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
877
878
879
880
        {
            result = _list[i];
            break;
        }
881
882
883
884
885
    }

    return result;
}

886
887
MediaServer* MediaServerList::getServerBySID( const char* sid )
{
888
889
    MediaServer* server = 0;

890
891
    for ( unsigned int i = 0; i < _list.size(); i++ )
    {
892
893
894
895
896
        if ( _list[i]->compareSID( sid ) )
        {
            server = _list[i];
            break;
        }
897
898
    }

899
900
901
    return server;
}

902
void MediaServerList::removeServer( const char* UDN )
903
904
905
906
{
    MediaServer* server = getServer( UDN );
    if ( !server ) return;

907
908
    msg_Dbg( _p_sd,
            "Removing server '%s'", server->getFriendlyName() );
909
910

    std::vector<MediaServer*>::iterator it;
911
    for ( it = _list.begin(); it != _list.end(); it++ )
912
    {
913
        if ( *it == server )
914
915
916
917
918
        {
            _list.erase( it );
            delete server;
            break;
        }
919
920
921
922
923
924
    }
}


// Item...

925
Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
926
927
{
    _parent = parent;
928

929
930
931
932
    _objectID = objectID;
    _title = title;
    _resource = resource;

933
934
935
936
937
938
939
    _inputItem = NULL;
}

Item::~Item()
{
    if(_inputItem)
        vlc_gc_decref( _inputItem );
940
941
}

942
const char* Item::getObjectID() const
943
944
945
946
{
    return _objectID.c_str();
}

947
const char* Item::getTitle() const
948
949
950
951
{
    return _title.c_str();
}

952
const char* Item::getResource() const
953
954
955
956
{
    return _resource.c_str();
}

957
void Item::setInputItem( input_item_t* p_input_item )
958
{
959
960
961
962
963
964
965
966
    if(_inputItem == p_input_item)
        return;

    if(_inputItem)
        vlc_gc_decref( _inputItem );

    vlc_gc_incref( p_input_item );
    _inputItem = p_input_item;
967
968
}

969
input_item_t* Item::getInputItem() const
970
{
971
    return _inputItem;
972
973
974
975
976
}


// Container...

977
978
979
Container::Container( Container*  parent,
                      const char* objectID,
                      const char* title )
980
981
{
    _parent = parent;
982

983
984
985
    _objectID = objectID;
    _title = title;

986
    _inputItem = NULL;
987
988
}

989
Container::~Container()
990
{
991
    for ( unsigned int i = 0; i < _containers.size(); i++ )
992
    {
993
        delete _containers[i];
994
995
    }

996
    for ( unsigned int i = 0; i < _items.size(); i++ )
997
    {
998
        delete _items[i];
999
    }
1000
1001
1002

    if(_inputItem )
        vlc_gc_decref( _inputItem );
1003
1004
}

1005
void Container::addItem( Item* item )
1006
1007
1008
1009
{
    _items.push_back( item );
}

1010
void Container::addContainer( Container* container )
1011
1012
1013
1014
{
    _containers.push_back( container );
}

1015
1016
1017
const char* Container::getObjectID() const
{
    return _objectID.c_str();
1018
1019
}

1020
1021
1022
const char* Container::getTitle() const
{
    return _title.c_str();
1023
1024
}

1025
1026
1027
unsigned int Container::getNumItems() const
{
    return _items.size();
1028
1029
}

1030
1031
1032
unsigned int Container::getNumContainers() const
{
    return _containers.size();
1033
1034
}