Add libupnp-based UPnP discovery from Christian Henz
[vlc.git] / modules / services_discovery / upnp_cc.cpp
1 /*****************************************************************************
2  * upnp_cc.cpp :  UPnP discovery module
3  *****************************************************************************
4  * Copyright (C) 2004-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org>
8  * 
9  * Based on original wxWindows patch for VLC, and dependant on CyberLink
10  * UPnP library from :
11  *          Satoshi Konno <skonno@cybergarage.org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 /*****************************************************************************
29  * Includes
30  *****************************************************************************/
31 #include <stdlib.h>                                      /* malloc(), free() */
32
33 #include <cybergarage/upnp/media/player/MediaPlayer.h>
34
35 #undef PACKAGE_NAME
36 #include <vlc/vlc.h>
37 #include <vlc/intf.h>
38
39 /* FIXME: thread-safety ?? */
40 /* FIXME: playlist locking */
41
42 /************************************************************************
43  * Macros and definitions
44  ************************************************************************/
45 using namespace std;
46 using namespace CyberLink;
47
48 /*****************************************************************************
49  * Module descriptor
50  *****************************************************************************/
51
52 /* Callbacks */
53     static int  Open ( vlc_object_t * );
54     static void Close( vlc_object_t * );
55
56 vlc_module_begin();
57     set_shortname( "UPnP");
58     set_description( _("Universal Plug'n'Play discovery") );
59     set_category( CAT_PLAYLIST );
60     set_subcategory( SUBCAT_PLAYLIST_SD );
61
62     set_capability( "services_discovery", 0 );
63     set_callbacks( Open, Close );
64
65 vlc_module_end();
66
67
68 /*****************************************************************************
69  * Local structures
70  *****************************************************************************/
71
72 struct services_discovery_sys_t
73 {
74     /* playlist node */
75     playlist_item_t *p_node;
76     playlist_t *p_playlist;
77 };
78
79 /*****************************************************************************
80  * Local prototypes
81  *****************************************************************************/
82
83 /* Main functions */
84     static void Run    ( services_discovery_t *p_sd );
85
86 /*****************************************************************************
87  * Open: initialize and create stuff
88  *****************************************************************************/
89 static int Open( vlc_object_t *p_this )
90 {
91     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
92     services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
93                                 malloc( sizeof( services_discovery_sys_t ) );
94
95     playlist_view_t     *p_view;
96     vlc_value_t         val;
97
98     p_sd->pf_run = Run;
99     p_sd->p_sys  = p_sys;
100
101     /* Create our playlist node */
102     p_sys->p_playlist = (playlist_t *)vlc_object_find( p_sd,
103                                                        VLC_OBJECT_PLAYLIST,
104                                                        FIND_ANYWHERE );
105     if( !p_sys->p_playlist )
106     {
107         msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening");
108         return VLC_EGENERIC;
109     }
110
111     p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY );
112     p_sys->p_node = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
113                                          "UPnP", p_view->p_root );
114     p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
115     p_sys->p_node->i_flags &= ~PLAYLIST_SKIP_FLAG;
116     val.b_bool = VLC_TRUE;
117     var_Set( p_sys->p_playlist, "intf-change", val );
118
119     return VLC_SUCCESS;
120 }
121
122
123 /*****************************************************************************
124  * Close:
125  *****************************************************************************/
126 static void Close( vlc_object_t *p_this )
127 {
128     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
129     services_discovery_sys_t    *p_sys  = p_sd->p_sys;
130
131     if( p_sys->p_playlist )
132     {
133         playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE,
134                              VLC_TRUE );
135         vlc_object_release( p_sys->p_playlist );
136     }
137
138     free( p_sys );
139 }
140
141 /*****************************************************************************
142  * Run: main UPnP thread
143  *****************************************************************************
144  * Processes UPnP events
145  *****************************************************************************/
146 class UPnPHandler : public MediaPlayer, public DeviceChangeListener,
147                     /*public EventListener,*/ public SearchResponseListener
148 {
149     private:
150         services_discovery_t *p_sd;
151         services_discovery_sys_t *p_sys;
152
153         Device *GetDeviceFromUSN( const string& usn )
154         {
155             return getDevice( usn.substr( 0, usn.find( "::" ) ).c_str() );
156         }
157
158         playlist_item_t *FindDeviceNode( Device *dev )
159         {
160             return playlist_ChildSearchName( p_sys->p_node, dev->getFriendlyName() );
161         }
162
163         playlist_item_t *FindDeviceNode( const string &usn )
164         {
165             return FindDeviceNode( GetDeviceFromUSN( usn ) );
166         }
167
168         playlist_item_t *AddDevice( Device *dev );
169         void AddDeviceContent( Device *dev );
170         void AddContent( playlist_item_t *p_parent, ContentNode *node );
171         void RemoveDevice( Device *dev );
172
173         /* CyberLink callbacks */
174         virtual void deviceAdded( Device *dev );
175         virtual void deviceRemoved( Device *dev );
176
177         virtual void deviceSearchResponseReceived( SSDPPacket *packet );
178         /*virtual void eventNotifyReceived( const char *uuid, long seq,
179                                           const char *name,
180                                           const char *value );*/
181
182     public:
183         UPnPHandler( services_discovery_t *p_this )
184             : p_sd( p_this ), p_sys( p_this->p_sys )
185         {
186             addDeviceChangeListener( this );
187             addSearchResponseListener( this );
188             //addEventListener( this );
189         }
190
191 };
192
193 static void Run( services_discovery_t *p_sd )
194 {
195     UPnPHandler u( p_sd );
196
197     u.start();
198
199     msg_Dbg( p_sd, "UPnP discovery started" );
200     /* read SAP packets */
201     while( !p_sd->b_die )
202     {
203         msleep( 500 );
204     }
205
206     u.stop();
207     msg_Dbg( p_sd, "UPnP discovery stopped" );
208 }
209
210
211 playlist_item_t *UPnPHandler::AddDevice( Device *dev )
212 {
213     if( dev == NULL )
214         return NULL;
215
216     /* We are not interested in IGD devices or whatever (at the moment) */
217     if ( !dev->isDeviceType( MediaServer::DEVICE_TYPE ) )
218         return NULL;
219
220     playlist_item_t *p_item = FindDeviceNode( dev );
221     if ( p_item != NULL )
222         return p_item;
223
224     /* FIXME:
225      * Maybe one day, VLC API will make sensible use of the const keyword;
226      * That day, you will no longer need this strdup().
227      */
228     char *str = strdup( dev->getFriendlyName( ) );
229
230     p_item = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
231                                   str, p_sys->p_node );
232     p_item->i_flags &= ~PLAYLIST_SKIP_FLAG;
233     msg_Dbg( p_sd, "device %s added", str );
234     free( str );
235
236     return p_item;
237 }
238
239 void UPnPHandler::AddDeviceContent( Device *dev )
240 {
241     playlist_item_t *p_devnode = AddDevice( dev );
242
243     if( p_devnode == NULL )
244         return;
245
246     AddContent( p_devnode, getContentDirectory( dev ) );
247 }
248
249 void UPnPHandler::AddContent( playlist_item_t *p_parent, ContentNode *node )
250 {
251     if( node == NULL )
252         return;
253
254     const char *title = node->getTitle();
255     if( title == NULL )
256         return;
257
258     msg_Dbg( p_sd, "title = %s", title );
259
260     if ( node->isItemNode() ) 
261     {
262         ItemNode *iNode = (ItemNode *)node;
263
264         playlist_item_t *p_item;
265         p_item = playlist_ItemNew( p_sd, iNode->getResource(), title );
266     
267         playlist_NodeAddItem( p_sys->p_playlist, p_item, VIEW_CATEGORY,
268                               p_parent, PLAYLIST_APPEND, PLAYLIST_END );
269
270     } else if ( node->isContainerNode() ) 
271     {
272         ContainerNode *conNode = (ContainerNode *)node;
273
274         char* p_name = strdup(title); /* See other comment on strdup */
275         playlist_item_t* p_node = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY, 
276                                                        p_name, p_parent );
277         free(p_name);
278
279         unsigned nContentNodes = conNode->getNContentNodes();
280
281         for( unsigned n = 0; n < nContentNodes; n++ )
282             AddContent( p_node, conNode->getContentNode( n ) );
283     }
284 }
285
286
287 void UPnPHandler::RemoveDevice( Device *dev )
288 {
289     playlist_item_t *p_item = FindDeviceNode( dev );
290
291     if( p_item != NULL )
292         playlist_NodeDelete( p_sys->p_playlist, p_item, VLC_TRUE, VLC_TRUE );
293 }
294
295
296 void UPnPHandler::deviceAdded( Device *dev )
297 {
298     msg_Dbg( p_sd, "adding device" );
299     AddDeviceContent( dev );
300 }
301
302
303 void UPnPHandler::deviceRemoved( Device *dev )
304 {
305     msg_Dbg( p_sd, "removing device" );
306     RemoveDevice( dev );
307 }
308
309
310 void UPnPHandler::deviceSearchResponseReceived( SSDPPacket *packet )
311 {
312     if( !packet->isRootDevice() )
313         return;
314
315     string usn, nts, nt, udn;
316
317     packet->getUSN( usn );
318     packet->getNT( nt );
319     packet->getNTS( nts );
320     udn = usn.substr( 0, usn.find( "::" ) );
321
322     /* Remove existing root device before adding updated one */
323
324     Device *dev = GetDeviceFromUSN( usn );
325     RemoveDevice( dev );
326
327     if( !packet->isByeBye() )
328         AddDeviceContent( dev );
329 }
330
331 /*void UPnPHandler::eventNotifyReceived( const char *uuid, long seq,
332                                        const char *name, const char *value )
333 {
334     msg_Dbg( p_sd, "event notify received" );
335     msg_Dbg( p_sd, "uuid = %s, name = %s, value = %s", uuid, name, value );
336 }*/