--- /dev/null
+/*****************************************************************************
+ * daap.c : Apple DAAP discovery module
+ *****************************************************************************
+ * Copyright (C) 2004 VideoLAN
+ * $Id: sap.c 9569 2004-12-15 22:17:52Z zorglub $
+ *
+ * Authors: Clément Stenac <zorglub@videolan.org>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Includes
+ *****************************************************************************/
+#include <stdlib.h> /* malloc(), free() */
+
+#include <vlc/vlc.h>
+#include <vlc/intf.h>
+
+#include "network.h"
+
+#include <vlc/input.h>
+
+#include <daap/client.h>
+
+/************************************************************************
+ * Macros and definitions
+ ************************************************************************/
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+/* Callbacks */
+ static int Open ( vlc_object_t * );
+ static void Close( vlc_object_t * );
+ static int OpenAccess ( vlc_object_t * );
+ static void CloseAccess( vlc_object_t * );
+
+vlc_module_begin();
+ set_description( _("DAAP shares") );
+ set_category( CAT_PLAYLIST );
+ set_subcategory( SUBCAT_PLAYLIST_SD );
+
+ set_capability( "services_discovery", 0 );
+ set_callbacks( Open, Close );
+
+ add_submodule();
+ set_description( _( "DAAP access") );
+ set_capability( "access2", 0 );
+ set_callbacks( OpenAccess, CloseAccess );
+vlc_module_end();
+
+
+/*****************************************************************************
+ * Local structures
+ *****************************************************************************/
+
+typedef struct host_s {
+ char *psz_name;
+ int i_id;
+
+ DAAP_SClientHost *p_host;
+ vlc_bool_t b_updated;
+ vlc_bool_t b_new;
+ int i_database_id;
+
+ playlist_item_t *p_node;
+
+ DAAP_ClientHost_DatabaseItem *p_songs;
+ int i_songs;
+} host_t;
+
+typedef struct daap_db_s {
+ host_t **pp_hosts;
+ int i_hosts;
+
+ int i_last_id;
+
+ vlc_mutex_t search_lock;
+} daap_db_t;
+
+struct services_discovery_sys_t {
+ playlist_item_t *p_node;
+
+ DAAP_SClient *p_client;
+ DAAP_SClientHost *p_host;
+
+ daap_db_t *p_db;
+};
+
+struct access_sys_t {
+ vlc_url_t url;
+
+ host_t *p_host;
+ int i_host;
+ int i_song;
+
+ daap_db_t *p_db;
+
+ DAAP_ClientHost_Song song;
+ DAAP_ClientHost_DatabaseItem songdata;
+ int i_orig_size;
+ void *p_orig_buffer;
+};
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+
+/* Main functions */
+ static void Run ( services_discovery_t *p_sd );
+ static void Callback( DAAP_SClient *p_client, DAAP_Status status,
+ int i_pos, void *p_context );
+ static int EnumerateCallback( DAAP_SClient *p_client,
+ DAAP_SClientHost *p_host,
+ void *p_context );
+ static void OnHostsUpdate( services_discovery_t *p_sd );
+ static void ProcessHost( services_discovery_t *p_sd, host_t *p_host );
+ static void FreeHost( services_discovery_t *p_sd, host_t *p_host );
+
+ static int Control( access_t *p_access, int i_query, va_list args );
+ static int Read( access_t *, uint8_t *, int );
+ static int Seek( access_t *, int64_t );
+
+/*****************************************************************************
+ * Open: initialize and create stuff
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+ services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+ services_discovery_sys_t *p_sys = (services_discovery_sys_t *)
+ malloc( sizeof( services_discovery_sys_t ) );
+
+ playlist_t *p_playlist;
+ playlist_view_t *p_view;
+ vlc_value_t val;
+
+ p_sd->pf_run = Run;
+ p_sd->p_sys = p_sys;
+
+ p_sys->p_db = (daap_db_t *)malloc( sizeof( daap_db_t ) );
+ if( !p_sys->p_db )
+ {
+ return VLC_EGENERIC;
+ }
+ p_sys->p_db->pp_hosts = NULL;
+ p_sys->p_db->i_hosts = 0;
+
+ var_Create( p_sd->p_vlc, "daap-db", VLC_VAR_ADDRESS );
+ val.p_address = p_sys->p_db;
+ var_Set( p_sd->p_vlc, "daap-db", val );
+
+ vlc_mutex_init( p_sd, &p_sys->p_db->search_lock );
+
+ /* Init DAAP */
+ p_sys->p_client = DAAP_Client_Create( Callback, p_sd );
+ p_sys->p_db->i_last_id = 0;
+
+ /* TODO: Set debugging correctly */
+// DAAP_Client_SetDebug( p_sys->p_client, "+trace" );
+
+
+ /* Create our playlist node */
+ p_playlist = (playlist_t *)vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
+ FIND_ANYWHERE );
+ if( !p_playlist )
+ {
+ msg_Warn( p_sd, "unable to find playlist, cancelling DAAP" );
+ return VLC_EGENERIC;
+ }
+
+ p_view = playlist_ViewFind( p_playlist, VIEW_CATEGORY );
+ p_sys->p_node = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
+ _("DAAP shares"), p_view->p_root );
+ p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
+
+ val.b_bool = VLC_TRUE;
+ var_Set( p_playlist, "intf-change", val );
+ vlc_object_release( p_playlist );
+
+ return VLC_SUCCESS;
+}
+
+static int OpenAccess( vlc_object_t *p_this )
+{
+ access_t *p_access = (access_t*)p_this;
+ access_sys_t *p_sys;
+ vlc_value_t val;
+ vlc_bool_t b_found = VLC_FALSE;
+ int i, i_ret;
+
+ p_access->pf_read = Read;
+ p_access->pf_block = NULL;
+ p_access->pf_control = Control;
+ p_access->pf_seek = Seek;
+ p_access->info.i_update = 0;
+ p_access->info.i_size = 0;
+ p_access->info.i_pos = 0;
+ p_access->info.b_eof = VLC_FALSE;
+ p_access->info.i_title = 0;
+ p_access->info.i_seekpoint = 0;
+ p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
+ memset( p_sys, 0, sizeof( access_sys_t ) );
+
+ i_ret = var_Get( p_access->p_vlc , "daap-db", &val );
+ p_sys->p_db = val.p_address;
+
+ if( p_sys->p_db == NULL || i_ret )
+ {
+ msg_Err( p_access, "The DAAP services_discovery module must be enabled" );
+ return VLC_EGENERIC;
+ }
+
+ vlc_UrlParse( &p_sys->url, p_access->psz_path, 0 );
+
+ p_sys->p_host = NULL;
+ p_sys->i_host = atoi( p_sys->url.psz_host ) ;
+ p_sys->i_song = p_sys->url.i_port;
+
+ if( !p_sys->i_host || !p_sys->i_song )
+ {
+ msg_Err( p_access, "invalid host or song" );
+ return VLC_EGENERIC;
+ }
+
+ /* Search the host */
+ vlc_mutex_lock( &p_sys->p_db->search_lock );
+ for( i = 0 ; i < p_sys->p_db->i_hosts ; i++ )
+ {
+ if( p_sys->p_db->pp_hosts[i]->i_id == p_sys->i_host )
+ {
+ p_sys->p_host = p_sys->p_db->pp_hosts[i];
+ break;
+ }
+ }
+ if( p_sys->p_host )
+ {
+ for( i = 0 ; i < p_sys->p_host->i_songs ; i++ )
+ {
+ if( p_sys->p_host->p_songs[i].id == p_sys->i_song )
+ {
+ p_sys->songdata = p_sys->p_host->p_songs[i];
+ b_found = VLC_TRUE;
+ break;
+ }
+ }
+ if( !b_found )
+ {
+ msg_Err( p_access, "invalid song (not found in %i)",
+ p_sys->p_host->i_songs );
+ }
+ }
+ else
+ {
+ msg_Warn( p_access, "invalid host (not found in %i)",
+ p_sys->p_db->i_hosts );
+ }
+ vlc_mutex_unlock( &p_sys->p_db->search_lock );
+
+ if( !p_sys->p_host || !b_found )
+ {
+ return VLC_EGENERIC;
+ }
+
+
+ msg_Dbg( p_access, "Downloading %s song %i (db %i)",
+ p_sys->songdata.songformat,
+ p_sys->i_song, p_sys->p_host->i_database_id );
+
+ /* FIXME: wait for better method by upstream */
+ i_ret = DAAP_ClientHost_GetAudioFile( p_sys->p_host->p_host,
+ p_sys->p_host->i_database_id,
+ p_sys->i_song,
+ p_sys->songdata.songformat,
+ &(p_sys->song) );
+
+ msg_Dbg( p_access, "Finished downloading, read %i bytes (ret %i)",
+ p_sys->song.size, i_ret );
+
+ p_access->info.i_size = p_sys->song.size;
+
+ if( i_ret != 0 )
+ return VLC_EGENERIC;
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+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;
+
+ playlist_t *p_playlist;
+ int i;
+
+ p_playlist = (playlist_t *) vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
+ FIND_ANYWHERE );
+
+ for( i = 0 ; i< p_sys->p_db->i_hosts ; i++ )
+ {
+ FreeHost( p_sd, p_sys->p_db->pp_hosts[i] );
+ }
+
+ var_Destroy( p_sd->p_vlc, "daap-db" );
+
+ if( p_playlist )
+ {
+ playlist_NodeDelete( p_playlist, p_sys->p_node, VLC_TRUE );
+ vlc_object_release( p_playlist );
+ }
+
+ free( p_sys );
+}
+
+static void CloseAccess( vlc_object_t *p_this )
+{
+ access_t *p_access = (access_t*) p_this;
+ access_sys_t *p_sys = p_access->p_sys;
+
+ if( p_sys )
+ {
+ if( p_sys->p_host )
+ {
+ p_sys->song.data = p_sys->p_orig_buffer;
+ p_sys->song.size = p_sys->i_orig_size;
+ DAAP_ClientHost_FreeAudioFile( p_sys->p_host->p_host, &p_sys->song );
+ }
+ free( p_sys );
+ }
+}
+
+/*****************************************************************************
+ * Run: main DAAP thread
+ *****************************************************************************/
+static void Run( services_discovery_t *p_sd )
+{
+ while( !p_sd->b_die )
+ {
+ msleep( 100000 );
+ }
+}
+
+/*****************************************************************************
+ * Access functions
+ *****************************************************************************/
+static int Control( access_t *p_access, int i_query, va_list args )
+{
+ vlc_bool_t *pb_bool;
+ int64_t *pi_64;
+ switch( i_query )
+ {
+ case ACCESS_CAN_SEEK:
+ case ACCESS_CAN_FASTSEEK:
+ pb_bool = (vlc_bool_t *)va_arg( args, vlc_bool_t *);
+ *pb_bool = VLC_TRUE;
+ break;
+ case ACCESS_CAN_PAUSE:
+ case ACCESS_CAN_CONTROL_PACE:
+ pb_bool = (vlc_bool_t *)va_arg( args, vlc_bool_t *);
+ *pb_bool = VLC_TRUE;
+ break;
+
+ case ACCESS_GET_PTS_DELAY:
+ pi_64 = (int64_t *)va_arg( args, int64_t *);
+ *pi_64 = (int64_t)300000;
+ break;
+
+ case ACCESS_SET_PAUSE_STATE:
+ break;
+
+ case ACCESS_GET_TITLE_INFO:
+ case ACCESS_SET_TITLE:
+ case ACCESS_SET_SEEKPOINT:
+ case ACCESS_SET_PRIVATE_ID_STATE:
+ return VLC_EGENERIC;
+
+ default:
+ msg_Warn( p_access, "unimplemented query control %i", i_query );
+ return VLC_EGENERIC;
+ }
+ return VLC_SUCCESS;
+}
+
+static int Read( access_t *p_access, uint8_t *p_buffer, int i_size )
+{
+ access_sys_t *p_sys = (access_sys_t *)p_access->p_sys;
+ int i_send;
+
+ if( i_size < p_sys->song.size && p_sys->song.size > 0 )
+ {
+ i_send = i_size;
+ }
+ else if( p_sys->song.size == 0 )
+ {
+ return 0;
+ }
+ else
+ {
+ i_send = p_sys->song.size;
+ }
+
+ memcpy( p_buffer, p_sys->song.data, i_send );
+ p_sys->song.size -= i_send;
+ p_sys->song.data += i_send;
+
+ return i_send;
+}
+
+static int Seek( access_t *p_access, int64_t i_pos )
+{
+ if( i_pos > p_access->p_sys->i_orig_size )
+ {
+ return VLC_EGENERIC;
+ }
+ p_access->p_sys->song.size = p_access->p_sys->i_orig_size - i_pos;
+ p_access->p_sys->song.data = p_access->p_sys->p_orig_buffer + i_pos;
+ return VLC_SUCCESS;
+}
+
+/**************************************************************
+ * Local functions
+ **************************************************************/
+static void Callback( DAAP_SClient *p_client, DAAP_Status status,
+ int i_pos, void *p_context )
+{
+ services_discovery_t *p_sd = (services_discovery_t *)p_context;
+
+ if( status == DAAP_STATUS_hostschanged )
+ {
+ OnHostsUpdate( p_sd );
+ }
+ else if( status == DAAP_STATUS_downloading )
+ {
+ }
+}
+
+static void OnHostsUpdate( services_discovery_t *p_sd )
+{
+ int i;
+
+ vlc_mutex_lock( &p_sd->p_sys->p_db->search_lock );
+ DAAP_Client_EnumerateHosts( p_sd->p_sys->p_client, EnumerateCallback, p_sd);
+ vlc_mutex_unlock( &p_sd->p_sys->p_db->search_lock );
+
+ /* FIXME: Handle the list better: remove old hosts, ... */
+ for( i = 0 ; i< p_sd->p_sys->p_db->i_hosts ; i ++ )
+ {
+ if( p_sd->p_sys->p_db->pp_hosts[i]->b_new )
+ ProcessHost( p_sd, p_sd->p_sys->p_db->pp_hosts[i] );
+ }
+}
+
+static int EnumerateCallback( DAAP_SClient *p_client,
+ DAAP_SClientHost *p_host,
+ void *p_context )
+{
+ int i;
+ int i_size = DAAP_ClientHost_GetSharename( p_host, NULL, 0 );
+ vlc_bool_t b_found = VLC_FALSE;
+ char *psz_buffer = (char *)malloc( i_size );
+ DAAP_ClientHost_GetSharename( p_host, psz_buffer, i_size );
+
+ services_discovery_t *p_sd = (services_discovery_t *)p_context;
+ services_discovery_sys_t *p_sys = p_sd->p_sys;
+
+ for( i = 0 ; i< p_sys->p_db->i_hosts; i++ )
+ {
+ if( !strcmp( p_sys->p_db->pp_hosts[i]->psz_name, psz_buffer ) )
+ {
+ p_sys->p_db->pp_hosts[i]->b_updated = VLC_TRUE;
+ b_found = VLC_TRUE;
+ break;
+ }
+ }
+
+ if( !b_found )
+ {
+ host_t *p_vlchost = (host_t *)malloc( sizeof( host_t ) );
+ p_vlchost->p_host = p_host;
+ p_vlchost->psz_name = psz_buffer;
+ p_vlchost->b_new = VLC_TRUE;
+ INSERT_ELEM( p_sys->p_db->pp_hosts, p_sys->p_db->i_hosts,
+ p_sys->p_db->i_hosts, p_vlchost );
+ }
+
+ return VLC_SUCCESS;
+}
+
+static void ProcessHost( services_discovery_t *p_sd, host_t *p_host )
+{
+ int i_dbsize, i_db, i, i_songsize, i_ret;
+ int i_size = DAAP_ClientHost_GetSharename( p_host->p_host, NULL, 0 );
+
+ playlist_t *p_playlist;
+
+ p_playlist = (playlist_t *) vlc_object_find( p_sd, VLC_OBJECT_PLAYLIST,
+ FIND_ANYWHERE );
+
+ if( !p_playlist )
+ {
+ return;
+ }
+
+ /* Connect to host */
+ if( p_host->b_new )
+ {
+ p_host->psz_name = (char *)malloc( i_size );
+ p_host->b_new = VLC_FALSE;
+ DAAP_ClientHost_GetSharename( p_host->p_host, p_host->psz_name ,
+ i_size );
+
+ msg_Dbg( p_sd, "new share %s", p_host->psz_name );
+ DAAP_ClientHost_AddRef( p_host->p_host );
+ i_ret = DAAP_ClientHost_Connect( p_host->p_host );
+ if( i_ret )
+ {
+ msg_Warn( p_sd, "unable to connect to DAAP host %s",
+ p_host->psz_name );
+// DAAP_ClientHost_Release( p_host->p_host );
+ vlc_object_release( p_playlist );
+ return;
+ }
+
+ p_host->p_node = playlist_NodeCreate( p_playlist, VIEW_CATEGORY,
+ p_host->psz_name,
+ p_sd->p_sys->p_node );
+ p_host->i_id = ++p_sd->p_sys->p_db->i_last_id;
+ }
+
+ /* Get DB */
+ i_dbsize = DAAP_ClientHost_GetDatabases( p_host->p_host, NULL, NULL, 0 );
+
+ DAAP_ClientHost_Database *p_database = malloc( i_dbsize );
+ DAAP_ClientHost_GetDatabases( p_host->p_host, p_database, &i_db, i_dbsize );
+
+ if( !i_db || !p_database )
+ {
+ msg_Warn( p_sd, "no database on DAAP host %s", p_host->psz_name );
+ vlc_object_release( p_playlist );
+ return;
+ }
+
+ /* We only use the first database */
+ p_host->i_database_id = p_database[0].id;
+
+ /* Get songs */
+ i_songsize = DAAP_ClientHost_GetDatabaseItems( p_host->p_host,
+ p_host->i_database_id,
+ NULL, NULL, 0 );
+ if( !i_songsize )
+ {
+ vlc_object_release( p_playlist );
+ return;
+ }
+ p_host->p_songs = malloc( i_songsize );
+
+ DAAP_ClientHost_GetDatabaseItems( p_host->p_host ,
+ p_host->i_database_id,
+ p_host->p_songs,
+ &p_host->i_songs, i_songsize );
+
+ for( i = 0; i< p_host->i_songs; i++ )
+ {
+ playlist_item_t *p_item;
+ int i_len = 7 + 10 + 1 + 10 ; /* "daap://" + host + ":" + song */
+ char *psz_buff = (char *)malloc( i_len );
+
+ snprintf( psz_buff, i_len, "daap://%i:%i", p_host->i_id,
+ p_host->p_songs[i].id );
+ p_item = playlist_ItemNew( p_sd, psz_buff,
+ p_host->p_songs[i].itemname );
+
+ playlist_NodeAddItem( p_playlist, p_item, VIEW_CATEGORY,
+ p_host->p_node, PLAYLIST_APPEND, PLAYLIST_END );
+
+ }
+
+ DAAP_ClientHost_AsyncWaitUpdate( p_host->p_host );
+
+ vlc_object_release( p_playlist );
+}
+
+static void FreeHost( services_discovery_t *p_sd, host_t *p_host )
+{
+ if( p_host->p_host )
+ {
+ DAAP_ClientHost_Disconnect( p_host->p_host );
+ DAAP_ClientHost_Release( p_host->p_host );
+ }
+
+ if( p_host->p_songs ) free( p_host->p_songs );
+}