mkv.cpp: I was wrong, the dirent export works
[vlc.git] / modules / services_discovery / daap.c
1 /*****************************************************************************
2  * daap.c :  Apple DAAP discovery module
3  *****************************************************************************
4  * Copyright (C) 2004 VideoLAN
5  * $Id: sap.c 9569 2004-12-15 22:17:52Z zorglub $
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Includes
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28
29 #include <vlc/vlc.h>
30 #include <vlc/intf.h>
31
32 #include "network.h"
33
34 #include <vlc/input.h>
35
36 #include <daap/client.h>
37
38 /************************************************************************
39  * Macros and definitions
40  ************************************************************************/
41
42 /*****************************************************************************
43  * Module descriptor
44  *****************************************************************************/
45
46 /* Callbacks */
47     static int  Open ( vlc_object_t * );
48     static void Close( vlc_object_t * );
49     static int  OpenAccess ( vlc_object_t * );
50     static void CloseAccess( vlc_object_t * );
51
52 vlc_module_begin();
53     set_description( _("DAAP shares") );
54     set_category( CAT_PLAYLIST );
55     set_subcategory( SUBCAT_PLAYLIST_SD );
56
57     set_capability( "services_discovery", 0 );
58     set_callbacks( Open, Close );
59
60     add_submodule();
61         set_description( _( "DAAP access") );
62         set_capability( "access2", 0 );
63         set_callbacks( OpenAccess, CloseAccess );
64 vlc_module_end();
65
66
67 /*****************************************************************************
68  * Local structures
69  *****************************************************************************/
70
71 typedef struct dhost_s {
72     char *psz_name;
73     int i_id;
74
75     DAAP_SClientHost *p_host;
76     vlc_bool_t b_updated;
77     vlc_bool_t b_new;
78     int i_database_id;
79
80     playlist_item_t *p_node;
81
82     DAAP_ClientHost_DatabaseItem *p_songs;
83     int i_songs;
84 } dhost_t;
85
86 typedef struct daap_db_s {
87     dhost_t **pp_hosts;
88     int       i_hosts;
89
90     int i_last_id;
91
92     vlc_mutex_t search_lock;
93 } daap_db_t;
94
95 struct services_discovery_sys_t {
96     playlist_item_t *p_node;
97
98     DAAP_SClient *p_client;
99     DAAP_SClientHost *p_host;
100
101     daap_db_t *p_db;
102 };
103
104 struct access_sys_t {
105     vlc_url_t url;
106
107     dhost_t *p_host;
108     int i_host;
109     int i_song;
110
111     daap_db_t *p_db;
112
113     DAAP_ClientHost_Song song;
114     DAAP_ClientHost_DatabaseItem songdata;
115     int i_orig_size;
116     void *p_orig_buffer;
117 };
118
119 /*****************************************************************************
120  * Local prototypes
121  *****************************************************************************/
122
123 /* Main functions */
124     static void Run    ( services_discovery_t *p_sd );
125     static void Callback( DAAP_SClient *p_client, DAAP_Status status,
126                           int i_pos, void *p_context );
127     static int EnumerateCallback( DAAP_SClient *p_client,
128                                   DAAP_SClientHost *p_host,
129                                   void *p_context );
130     static void OnHostsUpdate( services_discovery_t *p_sd );
131     static void ProcessHost( services_discovery_t *p_sd, dhost_t *p_host );
132     static void FreeHost( services_discovery_t *p_sd, dhost_t *p_host );
133
134     static int Control( access_t *p_access, int i_query, va_list args );
135     static int Read( access_t *, uint8_t *, int );
136     static int Seek( access_t *, int64_t );
137
138 /*****************************************************************************
139  * Open: initialize and create stuff
140  *****************************************************************************/
141 static int Open( vlc_object_t *p_this )
142 {
143     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
144     services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
145                                 malloc( sizeof( services_discovery_sys_t ) );
146
147     playlist_t          *p_playlist;
148     playlist_view_t     *p_view;
149     vlc_value_t         val;
150
151     p_sd->pf_run = Run;
152     p_sd->p_sys  = p_sys;
153
154     p_sys->p_db = (daap_db_t *)malloc( sizeof( daap_db_t ) );
155     if( !p_sys->p_db )
156     {
157         return VLC_EGENERIC;
158     }
159     p_sys->p_db->pp_hosts = NULL;
160     p_sys->p_db->i_hosts = 0;
161
162     var_Create( p_sd->p_vlc, "daap-db", VLC_VAR_ADDRESS );
163     val.p_address = p_sys->p_db;
164     var_Set( p_sd->p_vlc, "daap-db", val );
165
166     vlc_mutex_init( p_sd, &p_sys->p_db->search_lock );
167
168     /* Init DAAP */
169     p_sys->p_client = DAAP_Client_Create( Callback, p_sd );
170     p_sys->p_db->i_last_id = 0;
171
172     /* TODO: Set debugging correctly */
173 //    DAAP_Client_SetDebug( p_sys->p_client, "+trace" );
174
175
176     /* Create our playlist node */
177     p_playlist = (playlist_t *)vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
178                                                 FIND_ANYWHERE );
179     if( !p_playlist )
180     {
181         msg_Warn( p_sd, "unable to find playlist, cancelling DAAP" );
182         return VLC_EGENERIC;
183     }
184
185     p_view = playlist_ViewFind( p_playlist, VIEW_CATEGORY );
186     p_sys->p_node = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
187                                          _("DAAP shares"), p_view->p_root );
188     p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
189
190     val.b_bool = VLC_TRUE;
191     var_Set( p_playlist, "intf-change", val );
192     vlc_object_release( p_playlist );
193
194     return VLC_SUCCESS;
195 }
196
197 static int OpenAccess( vlc_object_t *p_this )
198 {
199     access_t     *p_access = (access_t*)p_this;
200     access_sys_t *p_sys;
201     vlc_value_t val;
202     vlc_bool_t b_found = VLC_FALSE;
203     int i, i_ret;
204
205     p_access->pf_read = Read;
206     p_access->pf_block = NULL;
207     p_access->pf_control = Control;
208     p_access->pf_seek = Seek;
209     p_access->info.i_update = 0;
210     p_access->info.i_size = 0;
211     p_access->info.i_pos = 0;
212     p_access->info.b_eof = VLC_FALSE;
213     p_access->info.i_title = 0;
214     p_access->info.i_seekpoint = 0;
215     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
216     memset( p_sys, 0, sizeof( access_sys_t ) );
217
218     i_ret = var_Get( p_access->p_vlc , "daap-db", &val );
219     p_sys->p_db = val.p_address;
220
221     if( p_sys->p_db == NULL || i_ret )
222     {
223         msg_Err( p_access, "The DAAP services_discovery module must be enabled" );
224         return VLC_EGENERIC;
225     }
226
227     vlc_UrlParse( &p_sys->url, p_access->psz_path, 0 );
228
229     p_sys->p_host = NULL;
230     p_sys->i_host = atoi( p_sys->url.psz_host ) ;
231     p_sys->i_song = p_sys->url.i_port;
232
233     if( !p_sys->i_host || !p_sys->i_song )
234     {
235         msg_Err( p_access, "invalid host or song" );
236         return VLC_EGENERIC;
237     }
238
239     /* Search the host */
240     vlc_mutex_lock( &p_sys->p_db->search_lock );
241     for( i = 0 ; i < p_sys->p_db->i_hosts ; i++ )
242     {
243         if( p_sys->p_db->pp_hosts[i]->i_id == p_sys->i_host )
244         {
245             p_sys->p_host = p_sys->p_db->pp_hosts[i];
246             break;
247         }
248     }
249     if( p_sys->p_host )
250     {
251        for( i = 0 ; i < p_sys->p_host->i_songs ; i++ )
252        {
253            if( p_sys->p_host->p_songs[i].id == p_sys->i_song )
254            {
255                p_sys->songdata = p_sys->p_host->p_songs[i];
256                b_found = VLC_TRUE;
257                break;
258            }
259        }
260        if( !b_found )
261        {
262            msg_Err( p_access, "invalid song (not found in %i)",
263                              p_sys->p_host->i_songs );
264        }
265     }
266     else
267     {
268         msg_Warn( p_access, "invalid host (not found in %i)",
269                              p_sys->p_db->i_hosts );
270     }
271     vlc_mutex_unlock( &p_sys->p_db->search_lock );
272
273     if( !p_sys->p_host || !b_found )
274     {
275         return VLC_EGENERIC;
276     }
277
278
279     msg_Dbg( p_access, "Downloading %s song %i (db %i)",
280                            p_sys->songdata.songformat,
281                            p_sys->i_song, p_sys->p_host->i_database_id );
282
283     /* FIXME: wait for better method by upstream */
284     i_ret = DAAP_ClientHost_GetAudioFile( p_sys->p_host->p_host,
285                                           p_sys->p_host->i_database_id,
286                                           p_sys->i_song,
287                                           p_sys->songdata.songformat,
288                                           &(p_sys->song) );
289
290     msg_Dbg( p_access, "Finished downloading, read %i bytes (ret %i)",
291                                           p_sys->song.size, i_ret );
292
293     p_access->info.i_size = p_sys->song.size;
294
295     if( i_ret != 0 )
296         return VLC_EGENERIC;
297
298     return VLC_SUCCESS;
299 }
300
301 /*****************************************************************************
302  * Close:
303  *****************************************************************************/
304 static void Close( vlc_object_t *p_this )
305 {
306     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
307     services_discovery_sys_t    *p_sys  = p_sd->p_sys;
308
309     playlist_t *p_playlist;
310     int i;
311
312     p_playlist = (playlist_t *) vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
313                                                  FIND_ANYWHERE );
314
315     for( i = 0 ; i< p_sys->p_db->i_hosts ; i++ )
316     {
317         FreeHost( p_sd, p_sys->p_db->pp_hosts[i] );
318     }
319
320     var_Destroy( p_sd->p_vlc, "daap-db" );
321
322     if( p_playlist )
323     {
324         playlist_NodeDelete( p_playlist, p_sys->p_node, VLC_TRUE, VLC_TRUE );
325         vlc_object_release( p_playlist );
326     }
327
328     free( p_sys );
329 }
330
331 static void CloseAccess( vlc_object_t *p_this )
332 {
333     access_t *p_access = (access_t*) p_this;
334     access_sys_t *p_sys = p_access->p_sys;
335
336     if( p_sys )
337     {
338         if( p_sys->p_host )
339         {
340             p_sys->song.data = p_sys->p_orig_buffer;
341             p_sys->song.size = p_sys->i_orig_size;
342             DAAP_ClientHost_FreeAudioFile( p_sys->p_host->p_host, &p_sys->song );
343         }
344         free( p_sys );
345     }
346 }
347
348 /*****************************************************************************
349  * Run: main DAAP thread
350  *****************************************************************************/
351 static void Run( services_discovery_t *p_sd )
352 {
353     while( !p_sd->b_die )
354     {
355         msleep( 100000 );
356     }
357 }
358
359 /*****************************************************************************
360  * Access functions
361  *****************************************************************************/
362 static int Control( access_t *p_access, int i_query, va_list args )
363 {
364     vlc_bool_t *pb_bool;
365     int64_t *pi_64;
366     switch( i_query )
367     {
368         case ACCESS_CAN_SEEK:
369         case ACCESS_CAN_FASTSEEK:
370             pb_bool = (vlc_bool_t *)va_arg( args, vlc_bool_t *);
371             *pb_bool = VLC_TRUE;
372             break;
373         case ACCESS_CAN_PAUSE:
374         case ACCESS_CAN_CONTROL_PACE:
375             pb_bool = (vlc_bool_t *)va_arg( args, vlc_bool_t *);
376             *pb_bool = VLC_TRUE;
377             break;
378
379         case ACCESS_GET_PTS_DELAY:
380             pi_64 = (int64_t *)va_arg( args, int64_t *);
381             *pi_64 = (int64_t)300000;
382             break;
383
384         case ACCESS_SET_PAUSE_STATE:
385             break;
386
387         case ACCESS_GET_TITLE_INFO:
388         case ACCESS_SET_TITLE:
389         case ACCESS_SET_SEEKPOINT:
390         case ACCESS_SET_PRIVATE_ID_STATE:
391             return VLC_EGENERIC;
392
393         default:
394             msg_Warn( p_access, "unimplemented query control %i", i_query );
395             return VLC_EGENERIC;
396     }
397     return VLC_SUCCESS;
398 }
399
400 static int Read( access_t *p_access, uint8_t *p_buffer, int i_size )
401 {
402     access_sys_t *p_sys = (access_sys_t *)p_access->p_sys;
403     int i_send;
404
405     if( i_size < p_sys->song.size && p_sys->song.size > 0 )
406     {
407         i_send = i_size;
408     }
409     else if( p_sys->song.size == 0 )
410     {
411         return 0;
412     }
413     else
414     {
415         i_send = p_sys->song.size;
416     }
417
418     memcpy( p_buffer, p_sys->song.data, i_send );
419     p_sys->song.size -= i_send;
420     p_sys->song.data += i_send;
421
422     return i_send;
423 }
424
425 static int Seek( access_t *p_access, int64_t i_pos )
426 {
427     if( i_pos > p_access->p_sys->i_orig_size )
428     {
429         return VLC_EGENERIC;
430     }
431     p_access->p_sys->song.size = p_access->p_sys->i_orig_size - i_pos;
432     p_access->p_sys->song.data = p_access->p_sys->p_orig_buffer + i_pos;
433     return VLC_SUCCESS;
434 }
435
436 /**************************************************************
437  * Local functions
438  **************************************************************/
439 static void Callback( DAAP_SClient *p_client, DAAP_Status status,
440                       int i_pos, void *p_context )
441 {
442     services_discovery_t *p_sd = (services_discovery_t *)p_context;
443
444     if( status == DAAP_STATUS_hostschanged )
445     {
446         OnHostsUpdate( p_sd );
447     }
448     else if( status == DAAP_STATUS_downloading )
449     {
450     }
451 }
452
453 static void OnHostsUpdate( services_discovery_t *p_sd )
454 {
455     int i;
456
457     for( i = 0 ; i< p_sd->p_sys->p_db->i_hosts ; i ++ )
458     {
459         p_sd->p_sys->p_db->pp_hosts[i]->b_updated = VLC_FALSE;
460         p_sd->p_sys->p_db->pp_hosts[i]->b_new     = VLC_FALSE;
461     }
462
463     vlc_mutex_lock( &p_sd->p_sys->p_db->search_lock );
464     DAAP_Client_EnumerateHosts( p_sd->p_sys->p_client, EnumerateCallback, p_sd);
465
466     for( i = 0 ; i< p_sd->p_sys->p_db->i_hosts ; i ++ )
467     {
468         if( p_sd->p_sys->p_db->pp_hosts[i]->b_updated == VLC_FALSE )
469         {
470             dhost_t *p_host = p_sd->p_sys->p_db->pp_hosts[i];
471             FreeHost( p_sd, p_host );
472             REMOVE_ELEM( p_sd->p_sys->p_db->pp_hosts,
473                          p_sd->p_sys->p_db->i_hosts, i );
474         }
475     }
476     vlc_mutex_unlock( &p_sd->p_sys->p_db->search_lock );
477
478     for( i = 0 ; i< p_sd->p_sys->p_db->i_hosts ; i ++ )
479     {
480         if( p_sd->p_sys->p_db->pp_hosts[i]->b_new )
481             ProcessHost( p_sd, p_sd->p_sys->p_db->pp_hosts[i] );
482     }
483 }
484
485 static int EnumerateCallback( DAAP_SClient *p_client,
486                               DAAP_SClientHost *p_host,
487                               void *p_context )
488 {
489     int i;
490     int i_size = DAAP_ClientHost_GetSharename( p_host, NULL, 0 );
491     vlc_bool_t b_found = VLC_FALSE;
492     char *psz_buffer = (char *)malloc( i_size );
493     DAAP_ClientHost_GetSharename( p_host, psz_buffer, i_size );
494
495     services_discovery_t *p_sd = (services_discovery_t *)p_context;
496     services_discovery_sys_t *p_sys = p_sd->p_sys;
497
498     for( i = 0 ; i< p_sys->p_db->i_hosts; i++ )
499     {
500         if( !strcmp( p_sys->p_db->pp_hosts[i]->psz_name, psz_buffer ) )
501         {
502             p_sys->p_db->pp_hosts[i]->b_updated = VLC_TRUE;
503             b_found = VLC_TRUE;
504             break;
505         }
506     }
507
508     if( !b_found )
509     {
510         dhost_t *p_vlchost = (dhost_t *)malloc( sizeof( dhost_t ) );
511         p_vlchost->p_node = NULL;
512         p_vlchost->p_host = p_host;
513         p_vlchost->psz_name = psz_buffer;
514         p_vlchost->b_new = VLC_TRUE;
515         p_vlchost->b_updated = VLC_TRUE;
516         INSERT_ELEM( p_sys->p_db->pp_hosts, p_sys->p_db->i_hosts,
517                      p_sys->p_db->i_hosts, p_vlchost );
518     }
519
520     return VLC_SUCCESS;
521 }
522
523 static void ProcessHost( services_discovery_t *p_sd, dhost_t *p_host )
524 {
525     int i_dbsize, i_db, i, i_songsize, i_ret;
526     int i_size = DAAP_ClientHost_GetSharename( p_host->p_host, NULL, 0 );
527
528     playlist_t *p_playlist;
529
530     p_playlist = (playlist_t *) vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
531                                                        FIND_ANYWHERE );
532
533     if( !p_playlist )
534     {
535         return;
536     }
537
538     /* Connect to host */
539     if( p_host->b_new )
540     {
541         p_host->psz_name = (char *)malloc( i_size );
542         p_host->b_new = VLC_FALSE;
543         DAAP_ClientHost_GetSharename( p_host->p_host, p_host->psz_name ,
544                                       i_size );
545
546         msg_Dbg( p_sd, "new share %s", p_host->psz_name );
547         DAAP_ClientHost_AddRef( p_host->p_host );
548         i_ret = DAAP_ClientHost_Connect( p_host->p_host );
549         if( i_ret )
550         {
551             msg_Warn( p_sd, "unable to connect to DAAP host %s",
552                              p_host->psz_name );
553 //            DAAP_ClientHost_Release( p_host->p_host );
554             vlc_object_release( p_playlist );
555             return;
556         }
557
558         p_host->p_node = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
559                                               p_host->psz_name,
560                                               p_sd->p_sys->p_node );
561         p_host->i_id = ++p_sd->p_sys->p_db->i_last_id;
562     }
563
564     /* Get DB */
565     i_dbsize = DAAP_ClientHost_GetDatabases( p_host->p_host, NULL, NULL, 0 );
566
567     DAAP_ClientHost_Database *p_database = malloc( i_dbsize );
568     DAAP_ClientHost_GetDatabases( p_host->p_host, p_database, &i_db, i_dbsize );
569
570
571     if( !i_db || !p_database )
572     {
573         msg_Warn( p_sd, "no database on DAAP host %s", p_host->psz_name );
574         vlc_object_release( p_playlist );
575         return;
576     }
577
578     /* We only use the first database */
579     p_host->i_database_id = p_database[0].id;
580
581     /* Get songs */
582     i_songsize = DAAP_ClientHost_GetDatabaseItems( p_host->p_host,
583                                                    p_host->i_database_id,
584                                                    NULL, NULL, 0 );
585     if( !i_songsize )
586     {
587         vlc_object_release( p_playlist );
588         return;
589     }
590     p_host->p_songs = malloc( i_songsize );
591
592     DAAP_ClientHost_GetDatabaseItems( p_host->p_host ,
593                                       p_host->i_database_id,
594                                       p_host->p_songs,
595                                       &p_host->i_songs, i_songsize );
596
597     for( i = 0; i< p_host->i_songs; i++ )
598     {
599         playlist_item_t *p_item;
600         int i_len = 7 + 10 + 1 + 10 ;    /* "daap://" + host + ":" + song */
601         char *psz_buff = (char *)malloc( i_len );
602
603         snprintf( psz_buff, i_len, "daap://%i:%i", p_host->i_id,
604                                                    p_host->p_songs[i].id );
605         p_item = playlist_ItemNew( p_sd, psz_buff,
606                                          p_host->p_songs[i].itemname );
607         vlc_input_item_AddInfo( &p_item->input, _("Meta-information"),
608                                 _("Artist"), p_host->p_songs[i].songartist );
609         vlc_input_item_AddInfo( &p_item->input, _("Meta-information"),
610                                 _("Album"), p_host->p_songs[i].songalbum );
611
612         playlist_NodeAddItem( p_playlist, p_item, VIEW_CATEGORY,
613                               p_host->p_node, PLAYLIST_APPEND, PLAYLIST_END );
614
615     }
616
617     DAAP_ClientHost_AsyncWaitUpdate( p_host->p_host );
618
619     vlc_object_release( p_playlist );
620 }
621
622 static void FreeHost( services_discovery_t *p_sd, dhost_t *p_host )
623 {
624     playlist_t *p_playlist;
625
626     if( p_host->p_host )
627     {
628         DAAP_ClientHost_Disconnect( p_host->p_host );
629         DAAP_ClientHost_Release( p_host->p_host );
630     }
631
632     p_playlist = (playlist_t *) vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
633                                                  FIND_ANYWHERE );
634     if( p_playlist )
635     {
636         if( p_host->p_node )
637             playlist_NodeDelete( p_playlist, p_host->p_node, VLC_TRUE ,
638                                                              VLC_TRUE);
639         vlc_object_release( p_playlist );
640     }
641
642     if( p_host->p_songs ) free( p_host->p_songs );
643 }