Mercurial > audlegacy-plugins
changeset 1407:776dd8fc2b38
DAAP plugin (work in progress)
author | Cristi Magherusan <majeru@atheme-project.org> |
---|---|
date | Sun, 05 Aug 2007 00:26:21 +0300 |
parents | fe0a9cf95642 |
children | a7db4a8a7b54 7b3290336f3b |
files | src/daap/Makefile src/daap/daap.c src/daap/xmms2-daap/Makefile src/daap/xmms2-daap/cc_handlers.c src/daap/xmms2-daap/cc_handlers.h src/daap/xmms2-daap/daap_cmd.c src/daap/xmms2-daap/daap_cmd.h src/daap/xmms2-daap/daap_conn.c src/daap/xmms2-daap/daap_conn.h src/daap/xmms2-daap/daap_md5.c src/daap/xmms2-daap/daap_md5.h src/daap/xmms2-daap/daap_mdns_avahi.c src/daap/xmms2-daap/daap_mdns_browse.h src/daap/xmms2-daap/daap_mdns_dnssd.c src/daap/xmms2-daap/daap_mdns_dummy.c src/daap/xmms2-daap/daap_util.c src/daap/xmms2-daap/daap_util.h src/daap/xmms2-daap/daap_xform.c src/daap/xmms2-daap/wscript |
diffstat | 19 files changed, 3923 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/Makefile Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,24 @@ +include ../../mk/rules.mk +include ../../mk/init.mk + +OBJECTIVE_LIBS = libdaap$(SHARED_SUFFIX) + +SUBDIRS = xmms2-daap + +LIBDIR = $(plugindir)/$(TRANSPORT_PLUGIN_DIR) + +LIBADD += ./xmms2-daap/xmms2-daap.a $(GTK_LIBS) $(GLIB_LIBS) $(PANGO_LIBS) + +SOURCES = daap.c + +OBJECTS = ${SOURCES:.c=.o} + +LIBDEP = ./xmms2-daap/xmms2-daap.a + +INC = -I../.. -I./xmms2-daap + +WARN = -Wall -pedantic -std=c99 + +CFLAGS += $(PICFLAGS) $(GTK_CFLAGS) $(GLIB_CFLAGS) $(PANGO_CFLAGS) $(INC) $(WARN) $(GCC42_CFLAGS) + +include ../../mk/objective.mk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/daap.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,129 @@ +/* Audacious DAAP transport plugin + * Copyright (c) 2007 Cristi Magherusan <majeru@gentoo.ro> + * + * With inspiration and code from David Hammerton's tunesbrowser + * + * 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 Softmcware + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <audacious/vfs.h> +#include <audacious/plugin.h> +/* +#include <audacious/configdb.h> +#include <libmowgli/mowgli.h> +#include <curl/curl.h> +*/ +#include <glib.h> +#include <daap/client.h> + + + + + + +DAAP_SClientHost *libopendaap_host; + + +VFSFile * daap_vfs_fopen_impl(const gchar * path, const gchar * mode) +{ + VFSFile *file; + file = g_new0(VFSFile, 1); + /* connectiong daap*/ + + + return file; +} + +gint daap_vfs_fclose_impl(VFSFile * file) +{ + return 0; +} +size_t daap_vfs_fread_impl(gpointer ptr, size_t size, size_t nmemb, VFSFile * file) +{ + return 0; +} + +size_t daap_vfs_fwrite_impl(gconstpointer ptr, size_t size, size_t nmemb, VFSFile * file) +{ + return -1; +} + +gint daap_vfs_getc_impl(VFSFile * stream) +{ + return 0; +} + +gint daap_vfs_ungetc_impl(gint c, VFSFile * stream) +{ + return 0; +} +gint daap_vfs_fseek_impl(VFSFile * file, glong offset, gint whence) +{ + return -1; +} + +void daap_vfs_rewind_impl(VFSFile * file) +{ + return; +} + +glong daap_vfs_ftell_impl(VFSFile * file) +{ + return 0; +} + +gboolean daap_vfs_feof_impl(VFSFile * file) +{ + return 1; +} + +gint daap_vfs_truncate_impl(VFSFile * file, glong size) +{ + return -1; +} +off_t daap_vfs_fsize_impl(VFSFile * file) +{ + return 0; +} +gchar *daap_vfs_metadata_impl(VFSFile * file, const gchar * field) +{ + return NULL; +} + +VFSConstructor daap_const = { + "daap://", + daap_vfs_fopen_impl, + daap_vfs_fclose_impl, + daap_vfs_fread_impl, + daap_vfs_fwrite_impl, + daap_vfs_getc_impl, + daap_vfs_ungetc_impl, + daap_vfs_fseek_impl, + daap_vfs_rewind_impl, + daap_vfs_ftell_impl, + daap_vfs_feof_impl, + daap_vfs_truncate_impl, + daap_vfs_fsize_impl, + daap_vfs_metadata_impl +}; + +static void init(void) +{ + vfs_register_transport(&daap_const); +} +static void cleanup(void) +{ +} +DECLARE_PLUGIN(daap, init, cleanup, NULL, NULL, NULL, NULL, NULL, NULL)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/Makefile Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,24 @@ +include ../../../mk/rules.mk +include ../../../mk/init.mk + +OBJECTIVE_LIBS_NOINST= xmms2-daap.a + +SOURCES= cc_handlers.c \ + daap_conn.c \ + daap_cmd.c \ + daap_md5.c \ + daap_util.c \ + daap_mdns_dnssd.c \ + daap_mdns_avahi.c \ + daap_mdns_dummy.c + +# daap_xform.c +LIBADD = $(GLIB_LIBS) + + +CFLAGS = $(GLIB_CFLAGS) -I/usr/include -std=c99 -Wall # -H -v + +#CFLAGS += $(PICFLAGS) $(ARCH_DEFINES) $(CURL_CFLAGS) -c -I../../../intl -I../../.. -I/usr/include -Wall -std=c99 -H -v +OBJECTS=${SOURCES:.c=.o} + +include ../../../mk/objective.mk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/cc_handlers.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,892 @@ +/** @file cc_handlers.c + * Functions for parsing DAAP content code data. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#define _GNU_SOURCE +#include <string.h> + +#include <stdlib.h> + +#include <glib.h> +#include <glib/gprintf.h> + +#include "cc_handlers.h" +#include "daap_conn.h" + +#define DMAP_BYTES_REMAINING ((gint) (data_end - current_data)) + +static void +endian_swap_int16 (gint16 *i) +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + gint16 tmp; + tmp = (gint16) (((*i >> 8) & 0x00FF) | + ((*i << 8) & 0xFF00)); + *i = tmp; +#endif +} + +static void +endian_swap_int32 (gint32 *i) +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + gint32 tmp; + tmp = (gint32) (((*i >> 24) & 0x000000FF) | + ((*i >> 8) & 0x0000FF00) | + ((*i << 8) & 0x00FF0000) | + ((*i << 24) & 0xFF000000)); + *i = tmp; +#endif +} + +static void +endian_swap_int64 (gint64 *i) +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + gint64 tmp; + tmp = (gint64) ( ((*i >> 40) & 0x00000000000000FF) | + ((*i >> 32) & 0x000000000000FF00) | + ((*i >> 24) & 0x0000000000FF0000) | + ((*i >> 8) & 0x00000000FF000000) | + G_GINT64_CONSTANT ((*i << 8) & 0x000000FF00000000) | + G_GINT64_CONSTANT ((*i << 24) & 0x0000FF0000000000) | + G_GINT64_CONSTANT ((*i << 32) & 0x00FF000000000000) | + G_GINT64_CONSTANT ((*i << 40) & 0xFF00000000000000)); + *i = tmp; +#endif +} + +static gint +grab_data_string (gchar **container, gchar *data, gint str_len) +{ + gint offset = 0; + + if (0 != str_len) { + *container = (gchar *) malloc (sizeof (gchar) * (str_len+1)); + + memcpy (*container, data + DMAP_CC_SZ + DMAP_INT_SZ, str_len); + (*container)[str_len] = '\0'; + + offset += str_len; + } + + return offset; +} + +static gint +grab_data_version (gint16 *cont_upper, gint16 *cont_lower, gchar *data) +{ + gint offset = DMAP_CC_SZ; + + memcpy (cont_lower, data + offset, DMAP_INT_SZ); + endian_swap_int16 (cont_lower); + offset += DMAP_INT_SZ; + + memcpy (cont_upper, data + offset, DMAP_INT_SZ); + endian_swap_int16 (cont_upper); + offset += DMAP_INT_SZ; + + return offset; +} + +static gint +grab_data (void *container, gchar *data, content_type ct) +{ + gint offset; + gint data_size; + + offset = DMAP_CC_SZ; + memcpy (&data_size, data + offset, DMAP_INT_SZ); + endian_swap_int32 ((gint32 *)&data_size); + offset += DMAP_INT_SZ; + + switch (ct) { + case DMAP_CTYPE_BYTE: + case DMAP_CTYPE_UNSIGNEDBYTE: + memcpy (container, data + offset, DMAP_BYTE_SZ); + offset += DMAP_BYTE_SZ; + break; + case DMAP_CTYPE_SHORT: + case DMAP_CTYPE_UNSIGNEDSHORT: + memcpy (container, data + offset, DMAP_SHORT_SZ); + endian_swap_int16 (container); + offset += DMAP_SHORT_SZ; + break; + case DMAP_CTYPE_INT: + case DMAP_CTYPE_UNSIGNEDINT: + memcpy (container, data + offset, DMAP_INT_SZ); + endian_swap_int32 (container); + offset += DMAP_INT_SZ; + break; + case DMAP_CTYPE_LONG: + case DMAP_CTYPE_UNSIGNEDLONG: + memcpy (container, data + offset, DMAP_LONG_SZ); + endian_swap_int64 (container); + offset += DMAP_LONG_SZ; + break; + case DMAP_CTYPE_STRING: + offset += grab_data_string ((gchar **) container, data, data_size); + break; + case DMAP_CTYPE_DATE: + memcpy (container, data + offset, DMAP_INT_SZ); + endian_swap_int32 (container); + offset += DMAP_INT_SZ; + break; + default: + g_print ("Warning: Unrecognized content type (%d).\n", ct); + break; + } + + return offset; +} + +static gint +cc_handler_mtco (cc_data_t *fields, gchar *current_data) +{ + gint offset = grab_data (&(fields->n_rec_matches), current_data, DMAP_CTYPE_INT); + return offset; +} + +static gint +cc_handler_mrco (cc_data_t *fields, gchar *current_data) +{ + gint offset = grab_data (&(fields->n_ret_items), current_data, DMAP_CTYPE_INT); + return offset; +} + +static gint +cc_handler_muty (cc_data_t *fields, gchar *current_data) +{ + gint offset = grab_data (&(fields->updt_type), current_data, DMAP_CTYPE_BYTE); + return offset; +} + +static gint +cc_handler_mstt (cc_data_t *fields, gchar *current_data) +{ + gint offset = grab_data (&(fields->status), current_data, DMAP_CTYPE_INT); + return offset; +} + +static gint +cc_handler_mlit (cc_data_t *fields, gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_item_record_t *item_fields; + + current_data = data + 8; + data_end = data + data_len; + + item_fields = g_malloc0 (sizeof (cc_item_record_t)); + + while (current_data < data_end && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','i','i','d'): + offset += grab_data (&(item_fields->dbid), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','p','e','r'): + offset += grab_data (&(item_fields->persistent_id), current_data, + DMAP_CTYPE_LONG); + break; + case CC_TO_INT ('m','i','n','m'): + offset += grab_data (&(item_fields->iname), current_data, + DMAP_CTYPE_STRING); + break; + + /* song list specific */ + case CC_TO_INT ('m','i','k','d'): + offset += grab_data (&(item_fields->item_kind), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('a','s','d','k'): + offset += grab_data (&(item_fields->song_data_kind), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('a','s','u','l'): + offset += grab_data (&(item_fields->song_data_url), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','a','l'): + offset += grab_data (&(item_fields->song_data_album), + current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','a','r'): + offset += grab_data (&(item_fields->song_data_artist), + current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','b','r'): + offset += grab_data (&(item_fields->song_bitrate), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','c','m'): + offset += grab_data (&(item_fields->song_comment), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','d','a'): + offset += grab_data (&(item_fields->song_date), current_data, + DMAP_CTYPE_DATE); + break; + case CC_TO_INT ('a','s','d','m'): + offset += grab_data (&(item_fields->song_date_mod), current_data, + DMAP_CTYPE_DATE); + break; + case CC_TO_INT ('a','s','g','n'): + offset += grab_data (&(item_fields->song_genre), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','f','m'): + offset += grab_data (&(item_fields->song_format), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','d','t'): + offset += grab_data (&(item_fields->song_description), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','s','r'): + offset += grab_data (&(item_fields->sample_rate), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','s','s','z'): + offset += grab_data (&(item_fields->song_size), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','s','s','t'): + offset += grab_data (&(item_fields->song_start_time), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','s','s','p'): + offset += grab_data (&(item_fields->song_stop_time), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','s','t','m'): + offset += grab_data (&(item_fields->song_total_time), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','s','y','r'): + offset += grab_data (&(item_fields->song_year), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','t','n'): + offset += grab_data (&(item_fields->song_track_no), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','c','p'): + offset += grab_data (&(item_fields->song_composer), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('a','s','t','c'): + offset += grab_data (&(item_fields->song_track_count), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','d','c'): + offset += grab_data (&(item_fields->song_disc_count), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','d','n'): + offset += grab_data (&(item_fields->song_disc_no), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','s','c','o'): + offset += grab_data (&(item_fields->song_compilation), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('a','s','b','t'): + offset += grab_data (&(item_fields->song_bpm), current_data, + DMAP_CTYPE_SHORT); + break; + case CC_TO_INT ('a','g','r','p'): + offset += grab_data (&(item_fields->song_grouping), current_data, + DMAP_CTYPE_STRING); + break; + case CC_TO_INT ('m','c','t','i'): + offset += grab_data (&(item_fields->container_id), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','u','d','l'): + offset += grab_data (&(item_fields->deleted_id), current_data, + DMAP_CTYPE_INT); + break; + + /* db list specific */ + case CC_TO_INT ('m','i','m','c'): + offset += grab_data (&(item_fields->db_n_items), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','c','t','c'): + offset += grab_data (&(item_fields->db_n_playlist), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('a','e','S','P'): + offset += grab_data (&(item_fields->is_smart_pl), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('a','b','p','l'): + offset += grab_data (&(item_fields->is_base_pl), current_data, + DMAP_CTYPE_BYTE); + break; + + /* exit conditions */ + case CC_TO_INT ('m','l','i','t'): + do_break = TRUE; + break; + default: + g_print ("Warning: Unrecognized content code " + "or end of data: %s\n", current_data); + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + fields->record_list = g_slist_prepend (fields->record_list, item_fields); + + return (gint) (current_data - data); +} + +static gint +cc_handler_mlcl (cc_data_t *fields, gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + current_data = data + 8; + data_end = data + data_len; + + while (current_data < data_end && !do_break) { + if (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3]) == + CC_TO_INT ('m','l','i','t')) { + + offset += cc_handler_mlit (fields, current_data, + DMAP_BYTES_REMAINING); + } else { + break; + } + + current_data += offset; + offset = 0; + } + + return (gint)(current_data - data); +} + +static cc_data_t * +cc_handler_adbs (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('m','u','t','y'): + offset += cc_handler_muty (fields, current_data); + break; + case CC_TO_INT ('m','t','c','o'): + offset += cc_handler_mtco (fields, current_data); + break; + case CC_TO_INT ('m','r','c','o'): + offset += cc_handler_mrco (fields, current_data); + break; + case CC_TO_INT ('m','l','c','l'): + offset += cc_handler_mlcl (fields, current_data, + DMAP_BYTES_REMAINING); + break; + default: + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_msrv (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('a','p','r','o'): + offset += grab_data_version (&(fields->daap_proto_major), + &(fields->daap_proto_minor), + current_data); + break; + case CC_TO_INT ('m','p','r','o'): + offset += grab_data_version (&(fields->dmap_proto_major), + &(fields->dmap_proto_minor), + current_data); + break; + case CC_TO_INT ('m','s','t','m'): + offset += grab_data (&(fields->timeout_interval), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','s','i','x'): + offset += grab_data (&(fields->has_indexing), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','e','x'): + offset += grab_data (&(fields->has_extensions), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','u','p'): + offset += grab_data (&(fields->has_update), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','a','l'): + offset += grab_data (&(fields->has_autologout), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','l','r'): + offset += grab_data (&(fields->login_required), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','q','y'): + offset += grab_data (&(fields->has_queries), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','r','s'): + offset += grab_data (&(fields->has_resolve), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','b','r'): + offset += grab_data (&(fields->has_browsing), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','p','i'): + offset += grab_data (&(fields->has_persistent), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','a','s'): + offset += grab_data (&(fields->auth_type), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('m','s','a','u'): + offset += grab_data (&(fields->auth_method), current_data, + DMAP_CTYPE_BYTE); + break; + case CC_TO_INT ('a','e','S','V'): + offset += grab_data (&(fields->sharing_version), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','s','d','c'): + offset += grab_data (&(fields->db_count), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','i','n','m'): + offset += grab_data (&(fields->server_name), current_data, + DMAP_CTYPE_STRING); + break; + default: + g_print ("Warning: Unrecognized content code " + "or end of data: %s\n", + current_data); + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_mccr (gchar *data, gint data_len) +{ + /* not implemented */ + return NULL; +} + +static cc_data_t * +cc_handler_mlog (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('m','l','i','d'): + offset += grab_data (&(fields->session_id), current_data, DMAP_CTYPE_INT); + break; + default: + g_print ("Unrecognized content code or end of data: %s\n", + current_data); + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_mupd (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','u','s','r'): + offset += grab_data (&(fields->revision_id), current_data, + DMAP_CTYPE_INT); + break; + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + default: + g_print ("Unrecognized content code or end of data: %s\n", + current_data); + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_avdb (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('m','u','t','y'): + offset += cc_handler_muty (fields, current_data); + break; + case CC_TO_INT ('m','t','c','o'): + offset += cc_handler_mtco (fields, current_data); + break; + case CC_TO_INT ('m','r','c','o'): + offset += cc_handler_mrco (fields, current_data); + break; + case CC_TO_INT ('m','l','c','l'): + offset += cc_handler_mlcl (fields, current_data, + DMAP_BYTES_REMAINING); + break; + default: + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_apso (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('m','u','t','y'): + offset += cc_handler_muty (fields, current_data); + break; + case CC_TO_INT ('m','t','c','o'): + offset += cc_handler_mtco (fields, current_data); + break; + case CC_TO_INT ('m','r','c','o'): + offset += cc_handler_mrco (fields, current_data); + break; + case CC_TO_INT ('m','l','c','l'): + offset += cc_handler_mlcl (fields, current_data, + DMAP_BYTES_REMAINING); + break; + default: + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +static cc_data_t * +cc_handler_aply (gchar *data, gint data_len) +{ + gint offset = 0; + gboolean do_break = FALSE; + gchar *current_data, *data_end; + cc_data_t *fields; + + current_data = data + 8; + data_end = data + data_len; + + fields = cc_data_new (); + + while ((current_data < data_end) && !do_break) { + switch (CC_TO_INT (current_data[0], current_data[1], + current_data[2], current_data[3])) { + case CC_TO_INT ('m','s','t','t'): + offset += cc_handler_mstt (fields, current_data); + break; + case CC_TO_INT ('m','u','t','y'): + offset += cc_handler_muty (fields, current_data); + break; + case CC_TO_INT ('m','t','c','o'): + offset += cc_handler_mtco (fields, current_data); + break; + case CC_TO_INT ('m','r','c','o'): + offset += cc_handler_mrco (fields, current_data); + break; + case CC_TO_INT ('m','l','c','l'): + offset += cc_handler_mlcl (fields, current_data, + DMAP_BYTES_REMAINING); + break; + default: + do_break = TRUE; + break; + } + + current_data += offset; + offset = 0; + } + + return fields; +} + +cc_data_t * +cc_data_new () +{ + cc_data_t *retval; + + retval = g_malloc0 (sizeof (cc_data_t)); + retval->record_list = NULL; + + return retval; +} + +void +cc_data_free (cc_data_t *fields) +{ + if (NULL != fields->server_name) g_free (fields->server_name); + + g_slist_foreach (fields->record_list, + (GFunc) cc_item_record_free, + NULL); + g_slist_free (fields->record_list); + + g_free (fields); +} + +void +cc_item_record_free (cc_item_record_t *item) +{ + if (NULL != item->iname) g_free (item->iname); + if (NULL != item->song_data_url) g_free (item->song_data_url); + if (NULL != item->song_data_album) g_free (item->song_data_album); + if (NULL != item->song_data_artist) g_free (item->song_data_artist); + if (NULL != item->song_comment) g_free (item->song_comment); + if (NULL != item->song_description) g_free (item->song_description); + if (NULL != item->song_genre) g_free (item->song_genre); + if (NULL != item->song_format) g_free (item->song_format); + if (NULL != item->song_composer) g_free (item->song_composer); + if (NULL != item->song_grouping) g_free (item->song_grouping); + + g_free (item); +} + +GSList * +cc_record_list_deep_copy (GSList *record_list) { + GSList *retval = NULL; + cc_item_record_t *record, *data; + + for (; record_list; record_list = g_slist_next (record_list)) { + data = record_list->data; + record = g_malloc0 (sizeof (cc_item_record_t)); + if (!record) { + g_print ("memory allocation failed for cc_record_list_deep_copy\n"); + return NULL; + } + + record->item_kind = data->item_kind; + record->song_data_kind = data->song_data_kind; + record->song_compilation = data->song_compilation; + record->is_smart_pl = data->is_smart_pl; + record->is_base_pl = data->is_base_pl; + + record->song_bitrate = data->song_bitrate; + record->song_year = data->song_year; + record->song_track_no = data->song_track_no; + record->song_track_count = data->song_track_count; + record->song_disc_count = data->song_disc_count; + record->song_disc_no = data->song_disc_no; + record->song_bpm = data->song_bpm; + + record->dbid = data->dbid; + record->sample_rate = data->sample_rate; + record->song_size = data->song_size; + record->song_start_time = data->song_start_time; + record->song_stop_time = data->song_stop_time; + record->song_total_time = data->song_total_time; + record->song_date = data->song_date; + record->song_date_mod = data->song_date_mod; + record->container_id = data->container_id; + + record->deleted_id = data->deleted_id; + + record->persistent_id = data->persistent_id; + + record->iname = g_strdup (data->iname); + record->song_data_url = g_strdup (data->song_data_url); + record->song_data_album = g_strdup (data->song_data_album); + record->song_data_artist = g_strdup (data->song_data_artist); + record->song_comment = g_strdup (data->song_comment); + record->song_description = g_strdup (data->song_description); + record->song_genre = g_strdup (data->song_genre); + record->song_format = g_strdup (data->song_format); + record->song_composer = g_strdup (data->song_composer); + record->song_grouping = g_strdup (data->song_grouping); + + /* db list specific */ + record->db_n_items = data->db_n_items; + record->db_n_playlist = data->db_n_playlist; + + retval = g_slist_prepend (retval, record); + } + + return retval; +} + +cc_data_t * +cc_handler (gchar *data, gint data_len) +{ + cc_data_t *retval; + + switch (CC_TO_INT (data[0],data[1],data[2],data[3])) { + case CC_TO_INT ('a','d','b','s'): + retval = cc_handler_adbs (data, data_len); + break; + case CC_TO_INT ('m','s','r','v'): + retval = cc_handler_msrv (data, data_len); + break; + case CC_TO_INT ('m','c','c','r'): + retval = cc_handler_mccr (data, data_len); + break; + case CC_TO_INT ('m','l','o','g'): + retval = cc_handler_mlog (data, data_len); + break; + case CC_TO_INT ('m','u','p','d'): + retval = cc_handler_mupd (data, data_len); + break; + case CC_TO_INT ('a','v','d','b'): + retval = cc_handler_avdb (data, data_len); + break; + case CC_TO_INT ('a','p','s','o'): + retval = cc_handler_apso (data, data_len); + break; + case CC_TO_INT ('a','p','l','y'): + retval = cc_handler_aply (data, data_len); + break; + default: + retval = NULL; + break; + } + + return retval; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/cc_handlers.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,174 @@ +/** @file cc_handlers.h + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#ifndef CC_HANDLERS_H +#define CC_HANDLERS_H + +#include <glib.h> + +#define CC_TO_INT(a,b,c,d) ((gint) ((a << 24) | \ + (b << 16) | \ + (c << 8) | \ + (d ) )) + +#define DMAP_CC_SZ (sizeof (gchar) * 4) +#define DMAP_BYTE_SZ sizeof (gint8) +#define DMAP_SHORT_SZ sizeof (gint16) +#define DMAP_INT_SZ sizeof (gint32) +#define DMAP_LONG_SZ sizeof (gint64) +#define DMAP_VERSION_SZ sizeof (gint16) + +#define DMAP_UNKNOWN_CC -1 + +typedef enum { + DMAP_CTYPE_BYTE = 1, + /* unconfirmed */ + DMAP_CTYPE_UNSIGNEDBYTE = 2, + + DMAP_CTYPE_SHORT = 3, + /* unconfirmed */ + DMAP_CTYPE_UNSIGNEDSHORT = 4, + + DMAP_CTYPE_INT = 5, + /* unconfirmed */ + DMAP_CTYPE_UNSIGNEDINT = 6, + + DMAP_CTYPE_LONG = 7, + /* unconfirmed */ + DMAP_CTYPE_UNSIGNEDLONG = 8, + + DMAP_CTYPE_STRING = 9, + DMAP_CTYPE_DATE = 10, + DMAP_CTYPE_VERSION = 11, + DMAP_CTYPE_LIST = 12, +} content_type; + +typedef struct { + /* items common to more than one type */ + gint8 updt_type; + + gint32 n_rec_matches; + gint32 n_ret_items; + gint32 status; + + GSList *record_list; + + /* msrv - server info */ + gint8 has_indexing; + gint8 has_extensions; + gint8 has_update; + gint8 has_autologout; + gint8 has_queries; + gint8 has_resolve; + gint8 has_browsing; + gint8 has_persistent; + gint8 auth_type; + gint8 auth_method; + gint8 login_required; + + gint16 daap_proto_major; + gint16 daap_proto_minor; + gint16 dmap_proto_major; + gint16 dmap_proto_minor; + + gint32 timeout_interval; + gint32 sharing_version; + gint32 db_count; + + gchar *server_name; + + /* mccr - content codes */ + /* none */ + + /* mlog - login */ + guint32 session_id; + + /* mupd - update */ + guint32 revision_id; + + /* avdb - db list */ + /* none */ + + /* apso - items in playlist */ + /* none */ + + /* aply - playlist list */ + /* none */ + +} cc_data_t; + +/* mlit -- used in a item listing */ +typedef struct { + gint8 item_kind; + gint8 song_data_kind; + gint8 song_compilation; + gint8 is_smart_pl; + gint8 is_base_pl; + + gint16 song_bitrate; + gint16 song_year; + gint16 song_track_no; + gint16 song_track_count; + gint16 song_disc_count; + gint16 song_disc_no; + gint16 song_bpm; + + gint32 dbid; + gint32 sample_rate; + gint32 song_size; + gint32 song_start_time; + gint32 song_stop_time; + gint32 song_total_time; + gint32 song_date; + gint32 song_date_mod; + gint32 container_id; + + gint32 deleted_id; + + guint64 persistent_id; + + gchar *iname; + gchar *song_data_url; + gchar *song_data_album; + gchar *song_data_artist; + gchar *song_comment; + gchar *song_description; + gchar *song_genre; + gchar *song_format; + gchar *song_composer; + gchar *song_grouping; + + /* db list specific */ + gint32 db_n_items; + gint32 db_n_playlist; + +} cc_item_record_t; + +cc_data_t * +cc_data_new (); + +void +cc_data_free (cc_data_t *fields); + +void +cc_item_record_free (cc_item_record_t *item); + +GSList * +cc_record_list_deep_copy (GSList *record_list); + +cc_data_t * +cc_handler (gchar *data, gint data_len); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_cmd.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,291 @@ +/** @file daap_cmd.c + * Wrapper functions for issuing DAAP commands. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#include "daap_cmd.h" +#include "daap_conn.h" + +static cc_data_t * +daap_request_data (GIOChannel *chan, const gchar *path, gchar *host, guint request_id); +static gboolean +daap_request_stream (GIOChannel *chan, gchar *path, gchar *host, + guint request_id, guint *size); +static gchar * +daap_url_append_meta (gchar *url, GSList *meta_list); + +guint +daap_command_login (gchar *host, gint port, guint request_id ) { + GIOChannel *chan; + cc_data_t *cc_data; + + guint session_id = 0; + + chan = daap_open_connection (host, port); + if (!chan) { + g_print("Connection to server failed! " + "Please make sure the url is of the form:\n" + "daap://ip[:port]/[song]"); + return 0; + } + + cc_data = daap_request_data (chan, "/login", host, request_id); + if (cc_data) { + session_id = cc_data->session_id; + cc_data_free (cc_data); + } + + g_io_channel_shutdown (chan, TRUE, NULL); + g_io_channel_unref (chan); + + return session_id; +} + +guint +daap_command_update (gchar *host, gint port, guint session_id, guint request_id) +{ + GIOChannel *chan; + gchar *request; + cc_data_t *cc_data; + guint revision_id = 0; + + chan = daap_open_connection (host, port); + if (!chan) { + return 0; + } + + request = g_strdup_printf ("/update?session-id=%d", session_id); + + cc_data = daap_request_data (chan, request, host, request_id); + if (cc_data) { + revision_id = cc_data->revision_id; + cc_data_free (cc_data); + } + + g_free (request); + g_io_channel_shutdown (chan, TRUE, NULL); + g_io_channel_unref (chan); + + return revision_id; +} + +gboolean +daap_command_logout (gchar *host, gint port, guint session_id, guint request_id) +{ + GIOChannel *chan; + gchar *request; + + chan = daap_open_connection (host, port); + if (!chan) { + return FALSE; + } + + request = g_strdup_printf ("/logout?session-id=%d", session_id); + + /* there is no cc_data generated, so we don't need to store it anywhere */ + daap_request_data (chan, request, host, request_id); + + g_free (request); + g_io_channel_shutdown (chan, TRUE, NULL); + g_io_channel_unref (chan); + + return TRUE; +} + +GSList * +daap_command_db_list (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id) +{ + GIOChannel *chan; + gchar *request; + cc_data_t *cc_data; + GSList *db_id_list = NULL; + + chan = daap_open_connection (host, port); + if (!chan) { + return NULL; + } + + request = g_strdup_printf ("/databases?session-id=%d&revision-id=%d", + session_id, revision_id); + + cc_data = daap_request_data (chan, request, host, request_id); + g_free (request); + if (cc_data) { + db_id_list = cc_record_list_deep_copy (cc_data->record_list); + cc_data_free (cc_data); + } + + g_io_channel_shutdown (chan, TRUE, NULL); + g_io_channel_unref (chan); + + return db_id_list; +} + +GSList * +daap_command_song_list (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id, gint db_id) +{ + GIOChannel *chan; + gchar *request; + cc_data_t *cc_data; + + GSList * song_list; + GSList * meta_items = NULL; + + chan = daap_open_connection (host, port); + if (!chan) { + return NULL; + } + + meta_items = g_slist_prepend (meta_items, g_strdup ("dmap.itemid")); + meta_items = g_slist_prepend (meta_items, g_strdup ("dmap.itemname")); + meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songartist")); + meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songformat")); + meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songtracknumber")); + meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songalbum")); + + request = g_strdup_printf ("/databases/%d/items?" + "session-id=%d&revision-id=%d", + db_id, session_id, revision_id); + + if (meta_items) { + request = daap_url_append_meta (request, meta_items); + } + + cc_data = daap_request_data (chan, request, host, request_id); + song_list = cc_record_list_deep_copy (cc_data->record_list); + + g_free (request); + cc_data_free (cc_data); + g_io_channel_shutdown (chan, TRUE, NULL); + g_io_channel_unref (chan); + g_slist_foreach (meta_items, (GFunc) g_free, NULL); + g_slist_free (meta_items); + + return song_list; +} + +GIOChannel * +daap_command_init_stream (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id, + gint dbid, gchar *song, guint *filesize) +{ + GIOChannel *chan; + gchar *request; + gboolean ok; + + chan = daap_open_connection (host, port); + if (!chan) { + return NULL; + } + + request = g_strdup_printf ("/databases/%d/items%s" + "?session-id=%d", + dbid, song, session_id); + + ok = daap_request_stream (chan, request, host, request_id, filesize); + g_free (request); + + if (!ok) { + return NULL; + } + + return chan; +} + +static cc_data_t * +daap_request_data (GIOChannel *chan, const gchar *path, gchar *host, guint request_id) +{ + guint status; + gchar *request, *header = NULL; + cc_data_t *retval; + + request = daap_generate_request (path, host, request_id); + daap_send_request (chan, request); + g_free (request); + + daap_receive_header (chan, &header); + if (!header) { + return NULL; + } + + status = get_server_status (header); + + switch (status) { + case UNKNOWN_SERVER_STATUS: + case HTTP_BAD_REQUEST: + case HTTP_FORBIDDEN: + case HTTP_NO_CONTENT: + case HTTP_NOT_FOUND: + retval = NULL; + break; + case HTTP_OK: + default: + retval = daap_handle_data (chan, header); + break; + } + g_free (header); + + return retval; +} + +static gboolean +daap_request_stream (GIOChannel *chan, gchar *path, gchar *host, + guint request_id, guint *size) +{ + guint status; + gchar *request, *header = NULL; + + request = daap_generate_request (path, host, request_id); + daap_send_request (chan, request); + g_free (request); + + daap_receive_header (chan, &header); + if (!header) { + return FALSE; + } + + status = get_server_status (header); + if (HTTP_OK != status) { + g_free (header); + return FALSE; + } + + *size = get_data_length (header); + + g_free (header); + + return TRUE; +} + +static gchar * +daap_url_append_meta (gchar *url, GSList *meta_list) +{ + gchar * tmpurl; + + tmpurl = url; + url = g_strdup_printf ("%s&meta=%s", url, (gchar *) meta_list->data); + g_free (tmpurl); + meta_list = g_slist_next (meta_list); + + for ( ; meta_list != NULL; meta_list = g_slist_next (meta_list)) { + tmpurl = url; + url = g_strdup_printf ("%s,%s", url, (gchar *) meta_list->data); + g_free (tmpurl); + } + + return url; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_cmd.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,112 @@ +/** @file daap_cmd.h + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#ifndef DAAP_CMD_H +#define DAAP_CMD_H + +#include "cc_handlers.h" + +/** + * Log into a DAAP server. + * Issue the command necessary for logging in. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param request_id the request id + * @return a session id for use in further commands + */ +guint +daap_command_login (gchar *host, gint port, guint request_id); + +/** + * Update the DAAP server status. + * Issue the command necessary for updating the connection to a DAAP server. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param session_id the id of the current session + * @param request_id the request id + * @return a revision id for use in further commands + */ +guint +daap_command_update (gchar *host, gint port, guint session_id, guint request_id); + +/** + * Log out of a DAAP server. + * Issue the command necessary for logging out. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param session_id the id of the current session + * @param request_id the request id + * @return TRUE on success, FALSE otherwise + */ +gboolean +daap_command_logout (gchar *host, gint port, guint session_id, guint request_id); + +/** + * Get a list of databases. + * Issue the command for fetching a list of song databases on the server. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param session_id the id of the current session + * @param revision_id the id of the current revision + * @param request_id the request id + * @return a list of database ids + */ +GSList * +daap_command_db_list (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id); + +/** + * Get a list of songs in a database. + * Issue the command for fetching a list of songs in a database on the server. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param session_id the id of the current session + * @param revision_id the id of the current revision + * @param request_id the request id + * @param db_id the database id + * @return a list of songs in the database + */ +GSList * +daap_command_song_list (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id, gint db_id); + +/** + * Begin streaming a song. + * Issue the command for streaming a song on the server. + * NOTE: This command only _begins_ the stream; unlike the other command + * functions, this one does not close the socket/channel, this must be done + * manually after reading the data. + * + * @param host host IP of server + * @param port port that the server uses on host + * @param session_id the id of the current session + * @param revision_id the id of the current revision + * @param request_id the request id + * @param dbid the database id + * @param song a string containing the id and file type of the song to stream + * @param filesize a pointer to an integer that stores the content length + * @return: a GIOChannel corresponding to streaming song data + */ +GIOChannel * +daap_command_init_stream (gchar *host, gint port, guint session_id, + guint revision_id, guint request_id, + gint dbid, gchar *song, guint *filesize); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_conn.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,331 @@ +/** @file daap_conn.c + * Manages the connection to a DAAP server. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <glib.h> +#include <glib/gprintf.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + + + + + + +#include "cc_handlers.h" +#include "daap_md5.h" +#include "daap_conn.h" +#include "daap_util.h" + + + GIOChannel * +daap_open_connection (gchar *host, gint port) +{ + gint ai_status; + gint sockfd; + struct sockaddr_in server; + struct addrinfo *ai_hint, *ai_result; + GIOChannel *sock_chan; + GError *err = NULL; + + sockfd = socket (AF_INET, SOCK_STREAM, 0); + if (sockfd == -1) { + return NULL; + } + + sock_chan = g_io_channel_unix_new (sockfd); + if (!g_io_channel_get_close_on_unref (sock_chan)) { + g_io_channel_set_close_on_unref (sock_chan, TRUE); + } + + g_io_channel_set_flags (sock_chan, G_IO_FLAG_NONBLOCK, &err); + if (NULL != err) { + g_print ("Error setting nonblock flag: %s\n", err->message); + g_io_channel_unref (sock_chan); + return NULL; + } + + /* call getaddrinfo() to convert a hostname to ip */ + + ai_hint = g_new0 (struct addrinfo, 1); + /* FIXME sometime in the future, we probably want to append + * " | {A,P}F_INET6" for IPv6 support */ + ai_hint->ai_family = AF_INET; + ai_hint->ai_protocol = PF_INET; + + while ((ai_status = getaddrinfo (host, NULL, ai_hint, &ai_result))) { + if (ai_status != EAI_AGAIN) { + g_print ("Error with getaddrinfo(): %s", gai_strerror (ai_status)); + g_io_channel_unref (sock_chan); + return NULL; + } + } + + memset (&server, 0, sizeof (struct sockaddr_in)); + + server.sin_addr = ((struct sockaddr_in *) ai_result->ai_addr)->sin_addr; + server.sin_family = AF_INET; + server.sin_port = htons (port); + + g_free (ai_hint); + freeaddrinfo (ai_result); + + while (42) { + fd_set fds; + struct timeval tmout; + gint sret; + gint err = 0; + guint errsize = sizeof (err); + + tmout.tv_sec = 3; + tmout.tv_usec = 0; + + sret = connect (sockfd, + (struct sockaddr *) &server, + sizeof (struct sockaddr_in)); + + if (sret == 0) { + break; + } else if (sret == -1 && errno != EINPROGRESS) { + g_print ("connect says: %s", strerror (errno)); + g_io_channel_unref (sock_chan); + return NULL; + } + + FD_ZERO (&fds); + FD_SET (sockfd, &fds); + + sret = select (sockfd + 1, NULL, &fds, NULL, &tmout); + if (sret == 0 || sret == SOCKET_ERROR) { + g_io_channel_unref (sock_chan); + return NULL; + } + + /** Haha, lol lol ololo sockets in POSIX */ + if (getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &err, &errsize) < 0) { + g_io_channel_unref (sock_chan); + return NULL; + } + + if (err != 0) { + g_print ("Connect call failed!"); + g_io_channel_unref (sock_chan); + return NULL; + } + + if (FD_ISSET (sockfd, &fds)) { + break; + } + } + + g_io_channel_set_encoding (sock_chan, NULL, &err); + if (NULL != err) { + g_print ("Error setting encoding: %s\n", err->message); + g_io_channel_unref (sock_chan); + return NULL; + } + + return sock_chan; +} + +gchar * +daap_generate_request (const gchar *path, gchar *host, gint request_id) +{ + gchar *req; + gint8 hash[33]; + + memset (hash, 0, 33); + + daap_hash_generate (DAAP_VERSION, (guchar *) path, 2, (guchar *) hash, + request_id); + + req = g_strdup_printf ("GET %s %s\r\n" + "Host: %s\r\n" + "Accept: */*\r\n" + "User-Agent: %s\r\n" + "Accept-Language: en-us, en;q=5.0\r\n" + "Client-DAAP-Access-Index: 2\r\n" + "Client-DAAP-Version: 3.0\r\n" + "Client-DAAP-Validation: %s\r\n" + "Client-DAAP-Request-ID: %d\r\n" + "Connection: close\r\n" + "\r\n", + path, HTTP_VER_STRING, host, + USER_AGENT, hash, request_id); + return req; +} + +void +daap_send_request (GIOChannel *sock_chan, gchar *request) +{ + gint n_bytes_to_send; + + n_bytes_to_send = strlen (request); + + write_buffer_to_channel (sock_chan, request, n_bytes_to_send); +} + +void +daap_receive_header (GIOChannel *sock_chan, gchar **header) +{ + guint n_total_bytes_recvd = 0; + gsize linelen; + gchar *response, *recv_line; + GIOStatus io_stat; + GError *err = NULL; + + if (NULL != header) { + *header = NULL; + } + + response = (gchar *) g_malloc0 (sizeof (gchar) * MAX_HEADER_LENGTH); + if (NULL == response) { + g_print ("Error: couldn't allocate memory for response.\n"); + return; + } + + /* read data from the io channel one line at a time, looking for + * the end of the header */ + do { + io_stat = g_io_channel_read_line (sock_chan, &recv_line, &linelen, + NULL, &err); + if (io_stat == G_IO_STATUS_ERROR) { + g_print ("Error reading from channel: %s\n", err->message); + break; + } + + if (NULL != recv_line) { + memcpy (response+n_total_bytes_recvd, recv_line, linelen); + n_total_bytes_recvd += linelen; + + if (strcmp (recv_line, "\r\n") == 0) { + g_free (recv_line); + if (NULL != header) { + *header = (gchar *) g_malloc0 (sizeof (gchar) * + n_total_bytes_recvd); + if (NULL == *header) { + g_print ("error: couldn't allocate header\n"); + break; + } + memcpy (*header, response, n_total_bytes_recvd); + } + break; + } + + g_free (recv_line); + } + + if (io_stat == G_IO_STATUS_EOF) { + break; + } + + if (n_total_bytes_recvd >= MAX_HEADER_LENGTH) { + g_print ("Warning: Maximum header size reached without finding " + "end of header; bailing.\n"); + break; + } + } while (TRUE); + + g_free (response); + + if (sock_chan) { + g_io_channel_flush (sock_chan, &err); + if (NULL != err) { + g_print ("Error flushing buffer: %s\n", err->message); + return; + } + } +} + +cc_data_t * +daap_handle_data (GIOChannel *sock_chan, gchar *header) +{ + cc_data_t * retval; + gint response_length; + gchar *response_data; + + response_length = get_data_length (header); + + if (BAD_CONTENT_LENGTH == response_length) { + g_print ("warning: Header does not contain a \""CONTENT_LENGTH + "\" parameter.\n"); + return NULL; + } else if (0 == response_length) { + g_print ("warning: "CONTENT_LENGTH" is zero, most likely the result of " + "a bad request.\n"); + return NULL; + } + + response_data = (gchar *) g_malloc0 (sizeof (gchar) * response_length); + if (NULL == response_data) { + g_print ("error: could not allocate response memory\n"); + return NULL; + } + + read_buffer_from_channel (sock_chan, response_data, response_length); + + retval = cc_handler (response_data, response_length); + g_free (response_data); + + return retval; +} + +gint +get_data_length (gchar *header) +{ + gint len; + gchar *content_length; + + content_length = strstr (header, CONTENT_LENGTH); + if (NULL == content_length) { + len = BAD_CONTENT_LENGTH; + } else { + content_length += strlen (CONTENT_LENGTH); + len = atoi (content_length); + } + + return len; +} + +gint +get_server_status (gchar *header) +{ + gint status; + gchar *server_status; + + server_status = strstr (header, HTTP_VER_STRING); + if (NULL == server_status) { + status = UNKNOWN_SERVER_STATUS; + } else { + server_status += strlen (HTTP_VER_STRING" "); + status = atoi (server_status); + } + + return status; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_conn.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,65 @@ +/** @file daap_conn.h + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#ifndef DAAP_CONN_H +#define DAAP_CONN_H + +#define MAX_REQUEST_LENGTH 1024 +#define MAX_HEADER_LENGTH (1024 * 16) + +#define BAD_CONTENT_LENGTH -1 + +#define DAAP_VERSION 3 + +#define HTTP_OK 200 +#define HTTP_NO_CONTENT 204 +#define HTTP_BAD_REQUEST 400 +#define HTTP_FORBIDDEN 403 +#define HTTP_NOT_FOUND 404 +#define UNKNOWN_SERVER_STATUS -1 + +#define DAAP_URL_PREFIX "daap://" +#define HTTP_VER_STRING "HTTP/1.1" +#define CONTENT_LENGTH "Content-Length: " +#define CONTENT_TYPE "Content-Type: " +/* TODO does this work ok? */ +#define USER_AGENT VERSION +/*#define USER_AGENT "iTunes/4.6 (Windows; N)"*/ + +GIOChannel * +daap_open_connection (gchar *host, gint port); + +gchar * +daap_generate_request (const gchar *path, gchar *host, gint request_id); + +void +daap_send_request (GIOChannel *sock_chan, gchar *request); + +void +daap_receive_header (GIOChannel *sock_chan, gchar **header); + +cc_data_t * +daap_handle_data (GIOChannel *sock_chan, gchar *header); + +void +daap_stream_data (GIOChannel *input, GIOChannel *output, gchar *header); + +gint +get_data_length (gchar *header); + +gint +get_server_status (gchar *header); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_md5.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,497 @@ +/** @file daap_md5.c + * + * Implementation of DAAP (iTunes Music Sharing) hashing, parsing, connection + * Slightly modified for use in XMMS2 + * + * Copyright (C) 2004,2005 Charles Schmidt <cschmidt2@emich.edu> + * + * 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-1307, USA. + * + */ + +#include <stdio.h> +#include <string.h> + +#include "daap_md5.h" + +/* hashing - based on/copied from libopendaap + * Copyright (c) 2004 David Hammerton + */ + +typedef struct { + guint32 buf[4]; + guint32 bits[2]; + unsigned char in[64]; + int version; +} MD5_CTX; + +/* +* This code implements the MD5 message-digest algorithm. +* The algorithm is due to Ron Rivest. This code was +* written by Colin Plumb in 1993, no copyright is claimed. +* This code is in the public domain; do with it what you wish. +* +* Equivalent code is available from RSA Data Security, Inc. +* This code has been tested against that, and is equivalent, +* except that you don't need to include two pages of legalese +* with every copy. +* +* To compute the message digest of a chunk of bytes, declare an MD5Context +* structure, pass it to OpenDaap_MD5Init, call OpenDaap_MD5Update as needed +* on buffers full of bytes, and then call OpenDaap_MD5Final, which will fill +* a supplied 16-byte array with the digest. +*/ +static void +MD5Transform (guint32 buf[4], + guint32 const in[16], + gint version); +/* for some reason we still have to reverse bytes on bigendian machines + * I don't really know why... but otherwise it fails.. + * Any MD5 gurus out there know why??? + */ +#if 0 //ndef WORDS_BIGENDIAN /* was: HIGHFIRST */ +#define byteReverse (buf, len) /* Nothing */ +#else +static void +byteReverse (unsigned char *buf, + unsigned longs); + +#ifndef ASM_MD5 +/* +* Note: this code is harmless on little-endian machines. +*/ +static void +byteReverse (unsigned char *buf, + unsigned longs) +{ + guint32 t; + do { + t = (guint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } while (--longs); +} +#endif /* ! ASM_MD5 */ +#endif /* #if 0 */ + +static void +OpenDaap_MD5Init (MD5_CTX *ctx, + gint version) +{ + memset (ctx, 0, sizeof (MD5_CTX)); + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + ctx->version = version; +} + +static void +OpenDaap_MD5Update (MD5_CTX *ctx, + unsigned char const *buf, + unsigned int len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + byteReverse (ctx->in, 16); + MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + byteReverse (ctx->in, 16); + MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy (ctx->in, buf, len); + +} + +static void +OpenDaap_MD5Final (MD5_CTX *ctx, + unsigned char digest[16]) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + byteReverse (ctx->in, 16); + MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version); + + /* Now fill the next block with 56 bytes */ + memset (ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + byteReverse (ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version); + byteReverse ((unsigned char *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); + memset (ctx, 0, sizeof (ctx)); /* In case it's sensitive */ + + return; +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1 (z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ +( w += f (x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* +* The core of the MD5 algorithm, this alters an existing MD5 hash to reflect +* the addition of 16 longwords of new data. OpenDaap_MD5Update blocks the +* data and converts bytes into longwords for this routine. +*/ +static void +MD5Transform (guint32 buf[4], + guint32 const in[16], + gint version) +{ + guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + + if (version == 1) { + MD5STEP (F2, b, c, d, a, in[8] + 0x445a14ed, 20); + } else { + MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + } + MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif + + + + + +static int staticHashDone = 0; +static unsigned char staticHash_42[256*65] = {0}; +static unsigned char staticHash_45[256*65] = {0}; + +static const char hexchars[] = "0123456789ABCDEF"; +static char ac[] = "Dpqzsjhiu!3114!Bqqmf!Dpnqvufs-!Jod/"; /* +1 */ +static gboolean ac_unfudged = FALSE; + +static void +DigestToString (const unsigned char *digest, + char *string) +{ + int i; + for (i = 0; i < 16; i++) { + unsigned char tmp = digest[i]; + string[i*2+1] = hexchars[tmp & 0x0f]; + string[i*2] = hexchars[(tmp >> 4) & 0x0f]; + } +} + +static void +GenerateStatic_42 () { + MD5_CTX ctx; + unsigned char *p = staticHash_42; + int i; + unsigned char buf[16]; + + for (i = 0; i < 256; i++) { + OpenDaap_MD5Init (&ctx, 0); + +#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen (str)) + + if ((i & 0x80) != 0) + MD5_STRUPDATE ("Accept-Language"); + else + MD5_STRUPDATE ("user-agent"); + + if ((i & 0x40) != 0) + MD5_STRUPDATE ("max-age"); + else + MD5_STRUPDATE ("Authorization"); + + if ((i & 0x20) != 0) + MD5_STRUPDATE ("Client-DAAP-Version"); + else + MD5_STRUPDATE ("Accept-Encoding"); + + if ((i & 0x10) != 0) + MD5_STRUPDATE ("daap.protocolversion"); + else + MD5_STRUPDATE ("daap.songartist"); + + if ((i & 0x08) != 0) + MD5_STRUPDATE ("daap.songcomposer"); + else + MD5_STRUPDATE ("daap.songdatemodified"); + + if ((i & 0x04) != 0) + MD5_STRUPDATE ("daap.songdiscnumber"); + else + MD5_STRUPDATE ("daap.songdisabled"); + + if ((i & 0x02) != 0) + MD5_STRUPDATE ("playlist-item-spec"); + else + MD5_STRUPDATE ("revision-number"); + + if ((i & 0x01) != 0) + MD5_STRUPDATE ("session-id"); + else + MD5_STRUPDATE ("content-codes"); +#undef MD5_STRUPDATE + + OpenDaap_MD5Final (&ctx, buf); + DigestToString (buf, (char *)p); + p += 65; + } +} + +static void GenerateStatic_45 () +{ + MD5_CTX ctx; + unsigned char *p = staticHash_45; + int i; + unsigned char buf[16]; + + for (i = 0; i < 256; i++) { + OpenDaap_MD5Init (&ctx, 1); + +#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen (str)) + + if ((i & 0x40) != 0) + MD5_STRUPDATE ("eqwsdxcqwesdc"); + else + MD5_STRUPDATE ("op[;lm,piojkmn"); + + if ((i & 0x20) != 0) + MD5_STRUPDATE ("876trfvb 34rtgbvc"); + else + MD5_STRUPDATE ("=-0ol.,m3ewrdfv"); + + if ((i & 0x10) != 0) + MD5_STRUPDATE ("87654323e4rgbv "); + else + MD5_STRUPDATE ("1535753690868867974342659792"); + + if ((i & 0x08) != 0) + MD5_STRUPDATE ("Song Name"); + else + MD5_STRUPDATE ("DAAP-CLIENT-ID:"); + + if ((i & 0x04) != 0) + MD5_STRUPDATE ("111222333444555"); + else + MD5_STRUPDATE ("4089961010"); + + if ((i & 0x02) != 0) + MD5_STRUPDATE ("playlist-item-spec"); + else + MD5_STRUPDATE ("revision-number"); + + if ((i & 0x01) != 0) + MD5_STRUPDATE ("session-id"); + else + MD5_STRUPDATE ("content-codes"); + + if ((i & 0x80) != 0) + MD5_STRUPDATE ("IUYHGFDCXWEDFGHN"); + else + MD5_STRUPDATE ("iuytgfdxwerfghjm"); + +#undef MD5_STRUPDATE + + OpenDaap_MD5Final (&ctx, buf); + DigestToString (buf, (char *)p); + p += 65; + } +} + +void +daap_hash_generate (short version_major, + const guchar *url, + guchar hash_select, + guchar *out, + gint request_id) +{ + unsigned char buf[16]; + MD5_CTX ctx; + int i; + + unsigned char *hashTable = (version_major == 3) ? + staticHash_45 : staticHash_42; + + if (!staticHashDone) { + GenerateStatic_42 (); + GenerateStatic_45 (); + staticHashDone = 1; + } + + OpenDaap_MD5Init (&ctx, (version_major == 3) ? 1 : 0); + + OpenDaap_MD5Update (&ctx, url, strlen ((const gchar*)url)); + if (ac_unfudged == FALSE) { + for (i = 0; i < strlen (ac); i++) { + ac[i] = ac[i]-1; + } + ac_unfudged = TRUE; + } + OpenDaap_MD5Update (&ctx, (const guchar*)ac, strlen (ac)); + + OpenDaap_MD5Update (&ctx, &hashTable[hash_select * 65], 32); + + if (request_id && version_major == 3) { + char scribble[20]; + sprintf (scribble, "%u", request_id); + OpenDaap_MD5Update (&ctx, (const guchar*)scribble, strlen (scribble)); + } + + OpenDaap_MD5Final (&ctx, buf); + DigestToString (buf, (char *)out); + + return; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_md5.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,37 @@ +/** @file daap_md5.h + * + * Header for DAAP (iTunes Music Sharing) hashing, connection + * Slightly modified for use in XMMS2 + * + * Copyright (C) 2004,2005 Charles Schmidt <cschmidt2@emich.edu> + * + * 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-1307, USA. + * + */ + +#ifndef DAAP_MD5_H +#define DAAP_MD5_H + +#include <glib.h> + +void +daap_hash_generate (short version_major, + const guchar *url, + guchar hash_select, + guchar *out, + gint request_id); + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_mdns_avahi.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,267 @@ +/** @file daap_mdns_browse.c + * Browser for DAAP servers shared via mDNS. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#include "daap_mdns_browse.h" + +#include <string.h> +#include <glib.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> + +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/timeval.h> + +#include <avahi-glib/glib-watch.h> +#include <avahi-glib/glib-malloc.h> + +#define ADDR_LEN (3 * 4 + 3 + 1) /* standard dotted-quad fmt */ + +typedef struct { + AvahiClient *client; + GMainLoop *mainloop; +} browse_callback_userdata_t; + +static GSList *g_server_list = NULL; +static GStaticMutex serv_list_mut = G_STATIC_MUTEX_INIT; +static AvahiGLibPoll *gl_poll = NULL; +static AvahiClient *client = NULL; +static AvahiServiceBrowser *browser = NULL; + +static GSList * +daap_mdns_serv_remove (GSList *serv_list, gchar *addr, guint port) +{ + GSList *first = serv_list; + daap_mdns_server_t *serv; + + for ( ; serv_list != NULL; serv_list = g_slist_next (serv_list)) { + serv = (daap_mdns_server_t *) serv_list->data; + if ( (port == serv->port) && (!strcmp (addr, serv->address)) ) { + serv_list = g_slist_remove (first, serv); + + g_free (serv->server_name); + g_free (serv->mdns_hostname); + g_free (serv->address); + g_free (serv); + + return serv_list; + } + } + return NULL; +} + +static void +daap_mdns_resolve_cb (AvahiServiceResolver *resolv, + AvahiIfIndex iface, + AvahiProtocol proto, + AvahiResolverEvent event, + const gchar *name, + const gchar *type, + const gchar *domain, + const gchar *hostname, + const AvahiAddress *addr, + guint16 port, + AvahiStringList *text, + AvahiLookupResultFlags flags, + void *userdata) +{ + gboolean *remove = userdata; + gchar ad[ADDR_LEN]; + daap_mdns_server_t *server; + + if (!resolv) { + return; + } + + switch (event) { + case AVAHI_RESOLVER_FOUND: + server = (daap_mdns_server_t *) + g_malloc0 (sizeof (daap_mdns_server_t)); + avahi_address_snprint (ad, sizeof (ad), addr); + + server->server_name = g_strdup (name); + server->address = g_strdup (ad); + server->mdns_hostname = g_strdup (hostname); + server->port = port; + + if (*remove) { + g_static_mutex_lock (&serv_list_mut); + g_server_list = daap_mdns_serv_remove (g_server_list, ad, port); + g_static_mutex_unlock (&serv_list_mut); + } else { + g_static_mutex_lock (&serv_list_mut); + g_server_list = g_slist_prepend (g_server_list, server); + g_static_mutex_unlock (&serv_list_mut); + } + g_free (remove); + + break; + + case AVAHI_RESOLVER_FAILURE: + break; + + default: + break; + } + + avahi_service_resolver_free (resolv); +} + +static void +daap_mdns_browse_cb (AvahiServiceBrowser *browser, + AvahiIfIndex iface, + AvahiProtocol proto, + AvahiBrowserEvent event, + const gchar *name, + const gchar *type, + const gchar *domain, + AvahiLookupResultFlags flags, + void *userdata) +{ + gboolean ok = FALSE; + gboolean *b = g_malloc (sizeof (gboolean)); + + AvahiClient *client = ((browse_callback_userdata_t *) userdata)->client; + + if (!browser) { + return; + } + + switch (event) { + case AVAHI_BROWSER_NEW: + *b = FALSE; + ok = (gboolean) + avahi_service_resolver_new (client, iface, proto, name, type, + domain, AVAHI_PROTO_UNSPEC, 0, + daap_mdns_resolve_cb, b); + break; + + case AVAHI_BROWSER_REMOVE: + *b = TRUE; + ok = (gboolean) + avahi_service_resolver_new (client, iface, proto, name, type, + domain, AVAHI_PROTO_UNSPEC, 0, + daap_mdns_resolve_cb, b); + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + + default: + break; + } +} + +static void +daap_mdns_client_cb (AvahiClient *client, + AvahiClientState state, + void * userdata) +{ + if (!client) { + return; + } + + switch (state) { + case AVAHI_CLIENT_FAILURE: + break; + default: + break; + } +} + +static void +daap_mdns_timeout (AvahiTimeout *to, void *userdata) +{ +} + +static gboolean +daap_mdns_timeout_glib (void *userdata) +{ + return FALSE; +} + +gboolean +daap_mdns_initialize () +{ + const AvahiPoll *av_poll; + + GMainLoop *ml = NULL; + + gboolean ok = TRUE; + gint errval; + struct timeval tv; + browse_callback_userdata_t *browse_userdata; + + if (gl_poll) { + ok = FALSE; + goto fail; + } + + browse_userdata = g_malloc0 (sizeof (browse_callback_userdata_t)); + + avahi_set_allocator (avahi_glib_allocator ()); + + ml = g_main_loop_new (NULL, FALSE); + + gl_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); + av_poll = avahi_glib_poll_get (gl_poll); + + avahi_elapse_time (&tv, 2000, 0); + av_poll->timeout_new (av_poll, &tv, daap_mdns_timeout, NULL); + g_timeout_add (5000, daap_mdns_timeout_glib, ml); + + client = avahi_client_new (av_poll, 0, daap_mdns_client_cb, ml, &errval); + if (!client) { + ok = FALSE; + goto fail; + } + + browse_userdata->client = client; + browse_userdata->mainloop = ml; + + browser = avahi_service_browser_new (client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, "_daap._tcp", NULL, + 0, daap_mdns_browse_cb, + browse_userdata); + if (!browser) { + ok = FALSE; + goto fail; + } + +fail: + return ok; +} + +GSList * +daap_mdns_get_server_list () +{ + GSList * l; + g_static_mutex_lock (&serv_list_mut); + l = g_slist_copy (g_server_list); + g_static_mutex_unlock (&serv_list_mut); + return l; +} + +void +daap_mdns_destroy () +{ + /* FIXME: deinit avahi */ +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_mdns_browse.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,37 @@ +/** @file daap_mdns_browse.h + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#ifndef DAAP_MDNS_BROWSE_H +#define DAAP_MDNS_BROWSE_H + +#include <glib.h> + +typedef struct { + gchar *server_name; + gchar *address; + gchar *mdns_hostname; + guint16 port; +} daap_mdns_server_t; + +gboolean +daap_mdns_initialize (); + +GSList * +daap_mdns_get_server_list (); + +void +daap_mdns_destroy (); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_mdns_dnssd.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,418 @@ +/** XMMS2 transform for accessing DAAP music shares. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#include <glib.h> +#include <dns_sd.h> +#include <string.h> + +#include "daap_mdns_browse.h" + +typedef struct GMDNS_t GMDNS; +typedef struct GMDNSServer_t GMDNSServer; +typedef struct GMDNSUserData_t GMDNSUserData; + +typedef void (*GMDNSFunc)(GMDNS *, gint, GMDNSServer *, gpointer); + +struct GMDNS_t { + GMutex *mutex; + GSList *service_list; + + GMDNSFunc callback; + gpointer user_data; + GMDNSUserData *browse_ud; +}; + +struct GMDNSUserData_t { + GMDNS *mdns; + GMDNSServer *server; + GPollFD *fd; + GSource *source; + + DNSServiceRef client; +}; + +struct GMDNSServer_t { + gchar *mdnsname; + gchar *hostname; + gchar *address; + GHashTable *txtvalues; + guint16 port; +}; + +enum { + G_MDNS_SERVER_ADD, + G_MDNS_SERVER_REMOVE +}; + +static void g_mdns_user_data_destroy (GMDNSUserData *ud); +static void g_mdns_server_destroy (GMDNSServer *server); +static gboolean g_mdns_poll_add (GMDNS *mdns, GMDNSUserData *ud, DNSServiceRef client); + +static GMDNS *g_mdns; + +static void +qr_reply (DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t ifIndex, + DNSServiceErrorType errorCode, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context) +{ + GMDNSUserData *ud = context; + gchar addr[1000]; + const guchar *rd = (guchar *)rdata; + + g_return_if_fail (ud); + g_return_if_fail (rrtype == kDNSServiceType_A); + + g_snprintf (addr, 1000, "%d.%d.%d.%d", rd[0], rd[1], rd[2], rd[3]); + + ud->server->address = g_strdup (addr); + + g_print ("adding server %s %s", ud->server->mdnsname, ud->server->address); + g_mutex_lock (ud->mdns->mutex); + ud->mdns->service_list = g_slist_prepend (ud->mdns->service_list, ud->server); + g_mutex_unlock (ud->mdns->mutex); + + if (ud->mdns->callback) { + ud->mdns->callback (ud->mdns, G_MDNS_SERVER_ADD, ud->server, ud->mdns->user_data); + } + g_mdns_user_data_destroy (ud); +} + + +static void +resolve_reply (DNSServiceRef client, + DNSServiceFlags flags, + uint32_t ifIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t opaqueport, + uint16_t txtLen, + const char *txtRecord, + void *context) +{ + GMDNSUserData *ud = context; + GMDNSUserData *ud2; + DNSServiceErrorType err; + gint i; + union { guint16 s; guchar b[2]; } portu = { opaqueport }; + + g_return_if_fail (ud); + + ud->server->port = ((guint16) portu.b[0]) << 8 | portu.b[1]; + ud->server->hostname = g_strdup (hosttarget); + ud->server->txtvalues = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + for (i = 0; i < TXTRecordGetCount (txtLen, txtRecord); i++) { + gchar key[256]; + const void *txt_value; + gchar *value; + guint8 vallen; + + err = TXTRecordGetItemAtIndex (txtLen, txtRecord, i, 256, key, &vallen, &txt_value); + if (err != kDNSServiceErr_NoError) { + g_warning ("error parsing TXT records!"); + } + + value = g_malloc (vallen + 1); + g_strlcpy (value, txt_value, vallen + 1); + g_hash_table_insert (ud->server->txtvalues, g_strdup (key), value); + } + + ud2 = g_new0 (GMDNSUserData, 1); + + err = DNSServiceQueryRecord (&ud2->client, 0, + kDNSServiceInterfaceIndexAny, + ud->server->hostname, + kDNSServiceType_A, + kDNSServiceClass_IN, + qr_reply, ud2); + + if (err != kDNSServiceErr_NoError) { + g_warning ("Error from QueryRecord!"); + } + + g_mdns_poll_add (ud->mdns, ud2, ud2->client); + ud2->server = ud->server; + + g_mdns_user_data_destroy (ud); +} + + +static void +browse_reply (DNSServiceRef client, + DNSServiceFlags flags, + uint32_t ifIndex, + DNSServiceErrorType errorCode, + const char *replyName, + const char *replyType, + const char *replyDomain, + void *context) +{ + DNSServiceErrorType err; + GMDNSServer *server; + GMDNSUserData *ud = context; + GMDNSUserData *ud2; + gboolean remove = (flags & kDNSServiceFlagsAdd) ? FALSE : TRUE; + + if (!remove) { + server = g_new0 (GMDNSServer, 1); + server->mdnsname = g_strdup (replyName); + ud2 = g_new0 (GMDNSUserData, 1); + err = DNSServiceResolve (&ud2->client, 0, kDNSServiceInterfaceIndexAny, + server->mdnsname, + "_daap._tcp", "local", + resolve_reply, ud2); + + if (err != kDNSServiceErr_NoError) { + g_warning ("Couldn't do ServiceResolv"); + g_free (server->mdnsname); + g_free (server); + return; + } + + ud2->server = server; + + g_mdns_poll_add (ud->mdns, ud2, ud2->client); + } else { + GSList *n, *nxt; + g_mutex_lock (ud->mdns->mutex); + for (n = ud->mdns->service_list; n; n = nxt) { + nxt = g_slist_next (n); + GMDNSServer *server = n->data; + if (strcmp (server->mdnsname, replyName) == 0) { + n = ud->mdns->service_list = g_slist_remove (ud->mdns->service_list, server); + g_mutex_unlock (ud->mdns->mutex); + if (ud->mdns->callback) + ud->mdns->callback (ud->mdns, G_MDNS_SERVER_REMOVE, server, ud->mdns->user_data); + g_mdns_server_destroy (server); + g_mutex_lock (ud->mdns->mutex); + } + } + g_mutex_unlock (ud->mdns->mutex); + } + +} + +static void +g_mdns_server_destroy (GMDNSServer *server) +{ + g_return_if_fail (server); + if (server->hostname) + g_free (server->hostname); + if (server->mdnsname) + g_free (server->mdnsname); + if (server->address) + g_free (server->address); + if (server->txtvalues) + g_hash_table_destroy (server->txtvalues); + + g_free (server); +} + +static gboolean +g_mdns_source_prepare (GSource *source, gint *timeout_) +{ + /* No timeout here */ + return FALSE; +} + +static gboolean +g_mdns_source_check (GSource *source) +{ + /* Maybe check for errors here? */ + return TRUE; +} + +static gboolean +g_mdns_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + GMDNSUserData *ud = user_data; + DNSServiceErrorType err; + + if ((ud->fd->revents & G_IO_ERR) || (ud->fd->revents & G_IO_HUP)) { + return FALSE; + } else if (ud->fd->revents & G_IO_IN) { + err = DNSServiceProcessResult (ud->client); + if (err != kDNSServiceErr_NoError) { + g_warning ("DNSServiceProcessResult returned error"); + return FALSE; + } + } + + return TRUE; +} + +static GSourceFuncs g_mdns_poll_funcs = { + g_mdns_source_prepare, + g_mdns_source_check, + g_mdns_source_dispatch, + NULL +}; + +static void +g_mdns_user_data_destroy (GMDNSUserData *ud) +{ + g_return_if_fail (ud); + + g_source_remove_poll (ud->source, ud->fd); + g_free (ud->fd); + g_source_destroy (ud->source); + DNSServiceRefDeallocate (ud->client); + g_free (ud); +} + +static gboolean +g_mdns_poll_add (GMDNS *mdns, GMDNSUserData *ud, DNSServiceRef client) +{ + ud->fd = g_new0 (GPollFD, 1); + ud->fd->fd = DNSServiceRefSockFD (client); + ud->client = client; + ud->mdns = mdns; + + if (ud->fd->fd == -1) { + g_free (ud->fd); + g_free (ud); + return FALSE; + } + + ud->fd->events = G_IO_IN | G_IO_HUP | G_IO_ERR; + + ud->source = g_source_new (&g_mdns_poll_funcs, sizeof (GSource)); + g_source_set_callback (ud->source, + (GSourceFunc) g_mdns_source_dispatch, + ud, NULL); + g_source_add_poll (ud->source, ud->fd); + g_source_attach (ud->source, NULL); + + return TRUE; +} + +/** + * Browse for a service. The callback will be called + * when it's fully resloved and queried + */ +static gboolean +g_mdns_browse (GMDNS *mdns, + gchar *service, + GMDNSFunc callback, + gpointer user_data) +{ + DNSServiceErrorType err; + DNSServiceRef client; + GMDNSUserData *ud; + + g_return_val_if_fail (!mdns->browse_ud, FALSE); + + ud = g_new0 (GMDNSUserData, 1); + + err = DNSServiceBrowse (&client, 0, kDNSServiceInterfaceIndexAny, + service, 0, browse_reply, ud); + + if (err != kDNSServiceErr_NoError) { + g_warning ("Couldn't setup mDNS poller"); + return FALSE; + } + + g_mdns_poll_add (mdns, ud, client); + + mdns->callback = callback; + mdns->user_data = user_data; + mdns->browse_ud = ud; + + return TRUE; +} + +/** + * Remove updates for browsing. Make sure to + * call this before you initialize a new browsing + */ +static gboolean +g_mdns_stop_browsing (GMDNS *mdns) +{ + g_return_val_if_fail (mdns, FALSE); + + g_mdns_user_data_destroy (mdns->browse_ud); + mdns->callback = NULL; + mdns->user_data = NULL; + + return TRUE; +} + +/** + * Return the full list of services + * the list is threadsafe but not the entries + * so it might be removed while you using it. + */ +GSList * +daap_mdns_get_server_list () +{ + GSList *ret=NULL, *n; + daap_mdns_server_t *server; + + g_mutex_lock (g_mdns->mutex); + for (n = g_mdns->service_list; n; n = g_slist_next (n)) { + GMDNSServer *s = n->data; + server = g_new0 (daap_mdns_server_t, 1); + server->mdns_hostname = s->mdnsname; + server->server_name = s->hostname; + server->port = s->port; + server->address = s->address; + ret = g_slist_append (ret, server); + } + g_mutex_unlock (g_mdns->mutex); + + return ret; +} + +/** + * Free resources held by GMDNS + */ +void +daap_mdns_destroy () +{ + GSList *n; + g_return_if_fail (g_mdns); + + g_mdns_stop_browsing (g_mdns); + + g_mutex_lock (g_mdns->mutex); + for (n = g_mdns->service_list; n; n = g_slist_next (n)) { + g_mdns_server_destroy (n->data); + } + g_mutex_unlock (g_mdns->mutex); + g_mutex_free (g_mdns->mutex); + + g_free (g_mdns); +} + +gboolean +daap_mdns_initialize () +{ + g_mdns = g_new0 (GMDNS, 1); + g_mdns->mutex = g_mutex_new (); + return g_mdns_browse (g_mdns, "_daap._tcp", NULL, NULL); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_mdns_dummy.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,36 @@ +/** XMMS2 transform for accessing DAAP music shares. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#include <glib.h> + +#include "daap_mdns_browse.h" + +gboolean +daap_mdns_initialize () +{ + return FALSE; +} + +GSList * +daap_mdns_get_server_list () +{ + return NULL; +} + +void +daap_mdns_destroy () +{ +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_util.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,79 @@ +/** @file daap_util.c + * Miscellaneous utility functions. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#include <glib.h> +#include <glib/gprintf.h> + +#include "daap_util.h" + +void +write_buffer_to_channel (GIOChannel *chan, gchar *buf, gint bufsize) +{ + guint total_sent_bytes = 0; + gsize sent_bytes; + GIOStatus io_stat; + GError *err = NULL; + + do { + io_stat = g_io_channel_write_chars (chan, + buf + total_sent_bytes, + bufsize - total_sent_bytes, + &sent_bytes, + &err); + if (io_stat == G_IO_STATUS_ERROR) { + if (NULL != err) { + g_print ("Error writing to channel: %s\n", err->message); + } + break; + } + + bufsize -= sent_bytes; + total_sent_bytes += sent_bytes; + } while (bufsize > 0); + + g_io_channel_flush (chan, &err); + if (NULL != err) { + g_print ("warning: error flushing channel: %s\n", err->message); + } +} + +gint +read_buffer_from_channel (GIOChannel *chan, gchar *buf, gint bufsize) +{ + guint n_total_bytes_read = 0; + gsize read_bytes; + GIOStatus io_stat; + GError *err = NULL; + + do { + io_stat = g_io_channel_read_chars (chan, + buf + n_total_bytes_read, + bufsize - n_total_bytes_read, + &read_bytes, + &err); + if (io_stat == G_IO_STATUS_ERROR) { + g_print ("warning: error reading from channel: %s\n", err->message); + } + n_total_bytes_read += read_bytes; + + if (io_stat == G_IO_STATUS_EOF) { + break; + } + } while (bufsize > n_total_bytes_read); + + return n_total_bytes_read; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_util.h Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,25 @@ +/** @file daap_util.h + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +#ifndef DAAP_UTIL_H +#define DAAP_UTIL_H + +gint +read_buffer_from_channel (GIOChannel *chan, gchar *buf, gint bufsize); + +void +write_buffer_to_channel (GIOChannel *chan, gchar *buf, gint bufsize); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/daap_xform.c Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,449 @@ +/** @file daap_xform.c + * XMMS2 transform for accessing DAAP music shares. + * + * Copyright (C) 2006-2007 XMMS2 Team + * + * 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. + */ + +/* XXX as of the current implementation, there is no method of logging out + * of the servers. + */ + +#include "xmms/xmms_xformplugin.h" +#include "xmms/xmms_log.h" + +#include "daap_cmd.h" +#include "daap_util.h" +#include "daap_mdns_browse.h" + +#include <stdlib.h> +#include <glib.h> +#include <glib/gprintf.h> + +#define DEFAULT_DAAP_PORT 3689 + +/* + * Type definitions + */ + +typedef struct { + gchar *host; + guint port; + + GIOChannel *channel; + + xmms_error_t status; +} xmms_daap_data_t; + +typedef struct { + gboolean logged_in; + + guint session_id; + guint revision_id; + guint request_id; +} xmms_daap_login_data_t; + +static GHashTable *login_sessions = NULL; + +/* + * Function prototypes + */ + +static gboolean +xmms_daap_plugin_setup (xmms_xform_plugin_t *xform_plugin); +static gboolean +xmms_daap_init (xmms_xform_t *xform); +static void +xmms_daap_destroy (xmms_xform_t *xform); +static gint +xmms_daap_read (xmms_xform_t *xform, void *buffer, + gint len, xmms_error_t *error); +static gboolean +xmms_daap_browse (xmms_xform_t *xform, const gchar *url, xmms_error_t *error); + +/* + * Plugin header + */ +XMMS_XFORM_PLUGIN ("daap", + "DAAP access plugin", + "SoC", + "Accesses iTunes (DAAP) music shares", + xmms_daap_plugin_setup); + + +/** + * Extract hostname, port and command from an url. + * daap://hostname:port/command + */ +static gboolean +get_data_from_url (const gchar *url, gchar **host, guint *port, gchar **cmd, xmms_error_t *err) +{ + const gchar *port_ptr, *cmd_ptr, *end_ptr, *stripped; + + stripped = url + sizeof (gchar) * strlen ("daap://"); + + end_ptr = stripped + sizeof (gchar) * strlen (stripped); + + if (stripped == end_ptr) { + xmms_error_set (err, XMMS_ERROR_INVAL, "Empty URL"); + return FALSE; + } + + port_ptr = strstr (stripped, ":"); + if (port && port_ptr && (port_ptr + 1) != end_ptr) { + *port = strtol (port_ptr + 1, (gchar **) NULL, 10); + if (*port == 0) { + *port = DEFAULT_DAAP_PORT; + } + } else if (port) { + *port = DEFAULT_DAAP_PORT; + } + + cmd_ptr = strstr (stripped, "/"); + if (cmd && cmd_ptr && (cmd_ptr + 1) != end_ptr) { + *cmd = g_strdup (cmd_ptr); + } else if (cmd) { + /* cmd wanted but not found */ + xmms_error_set (err, XMMS_ERROR_INVAL, "No file requested"); + } else if (!cmd && cmd_ptr && (cmd_ptr + 1) != end_ptr) { + /* cmd not wanted but found */ + xmms_error_set (err, XMMS_ERROR_NOENT, "No such directory"); + return FALSE; + } + + if (port_ptr) { + *host = g_strndup (stripped, port_ptr - stripped); + } else if (cmd_ptr) { + *host = g_strndup (stripped, cmd_ptr - stripped); + } else { + *host = g_strdup (stripped); + } + + return TRUE; +} + + +static gboolean +xmms_daap_plugin_setup (xmms_xform_plugin_t *xform_plugin) +{ + xmms_xform_methods_t methods; + + XMMS_XFORM_METHODS_INIT (methods); + methods.init = xmms_daap_init; + methods.destroy = xmms_daap_destroy; + methods.read = xmms_daap_read; + methods.browse = xmms_daap_browse; + + xmms_xform_plugin_methods_set (xform_plugin, &methods); + + xmms_xform_plugin_indata_add (xform_plugin, + XMMS_STREAM_TYPE_MIMETYPE, + "application/x-url", + XMMS_STREAM_TYPE_URL, + "daap://*", + XMMS_STREAM_TYPE_END); + + daap_mdns_initialize (); + + if (!login_sessions) { + login_sessions = g_hash_table_new (g_str_hash, g_str_equal); + } + + return TRUE; +} + + +/** + * Add a song to the browsing list. + */ +static void +daap_add_song_to_list (xmms_xform_t *xform, cc_item_record_t *song) +{ + gchar *songurl; + + songurl = g_strdup_printf ("%u.%s", song->dbid, song->song_format); + xmms_xform_browse_add_entry (xform, songurl, 0); + g_free (songurl); + + if (song->iname) { + xmms_xform_browse_add_entry_property_str (xform, "title", + song->iname); + } + + if (song->song_data_artist) { + xmms_xform_browse_add_entry_property_str (xform, "artist", + song->song_data_artist); + } + + if (song->song_data_album) { + xmms_xform_browse_add_entry_property_str (xform, "album", + song->song_data_album); + } + + xmms_xform_browse_add_entry_property_int (xform, "tracknr", + song->song_track_no); +} + + +/** + * Scan a daap server for songs. + */ +static gboolean +daap_get_urls_from_server (xmms_xform_t *xform, gchar *host, guint port, + xmms_error_t *err) +{ + GSList *dbid_list = NULL; + GSList *song_list = NULL, *song_el; + cc_item_record_t *db_data; + xmms_daap_login_data_t *login_data; + gchar *hash; + + hash = g_strdup_printf ("%s:%u", host, port); + + login_data = g_hash_table_lookup (login_sessions, hash); + + if (!login_data) { + login_data = g_new0 (xmms_daap_login_data_t, 1); + + login_data->session_id = daap_command_login (host, port, 0, err); + if (xmms_error_iserror (err)) { + return FALSE; + } + + login_data->revision_id = daap_command_update (host, port, + login_data->session_id, + 0); + + login_data->request_id = 1; + login_data->logged_in = TRUE; + + g_hash_table_insert (login_sessions, hash, login_data); + } else { + login_data->revision_id = daap_command_update (host, port, + login_data->session_id, + 0); + } + + dbid_list = daap_command_db_list (host, port, login_data->session_id, + login_data->revision_id, 0); + if (!dbid_list) { + return FALSE; + } + + /* XXX i've never seen more than one db per server out in the wild, + * let's hope that never changes *wink* + * just use the first db in the list */ + db_data = (cc_item_record_t *) dbid_list->data; + song_list = daap_command_song_list (host, port, login_data->session_id, + login_data->revision_id, + 0, db_data->dbid); + + g_slist_foreach (dbid_list, (GFunc) cc_item_record_free, NULL); + g_slist_free (dbid_list); + + if (!song_list) { + return FALSE; + } + + for (song_el = song_list; song_el; song_el = g_slist_next (song_el)) { + daap_add_song_to_list (xform, song_el->data); + } + + g_slist_foreach (song_list, (GFunc) cc_item_record_free, NULL); + g_slist_free (song_list); + + return TRUE; +} + + +/* + * Member functions + */ + +static gboolean +xmms_daap_init (xmms_xform_t *xform) +{ + gint dbid; + GSList *dbid_list = NULL; + xmms_daap_data_t *data; + xmms_daap_login_data_t *login_data; + xmms_error_t err; + const gchar *url; + const gchar *metakey; + gchar *command, *hash; + guint filesize; + + g_return_val_if_fail (xform, FALSE); + + url = xmms_xform_indata_get_str (xform, XMMS_STREAM_TYPE_URL); + + g_return_val_if_fail (url, FALSE); + + data = g_new0 (xmms_daap_data_t, 1); + + xmms_error_reset (&err); + + if (!get_data_from_url (url, &(data->host), &(data->port), &command, &err)) { + return FALSE; + } + + hash = g_strdup_printf ("%s:%u", data->host, data->port); + + login_data = g_hash_table_lookup (login_sessions, hash); + if (!login_data) { + XMMS_DBG ("creating login data for %s", hash); + login_data = g_new0 (xmms_daap_login_data_t, 1); + + login_data->request_id = 1; + login_data->logged_in = TRUE; + + login_data->session_id = daap_command_login (data->host, data->port, + login_data->request_id, + &err); + if (xmms_error_iserror (&err)) { + return FALSE; + } + + g_hash_table_insert (login_sessions, hash, login_data); + } + + login_data->revision_id = daap_command_update (data->host, data->port, + login_data->session_id, + login_data->request_id); + dbid_list = daap_command_db_list (data->host, data->port, + login_data->session_id, + login_data->revision_id, + login_data->request_id); + if (!dbid_list) { + return FALSE; + } + + /* XXX: see XXX in the browse function above */ + dbid = ((cc_item_record_t *) dbid_list->data)->dbid; + /* want to request a stream, but don't read the data yet */ + data->channel = daap_command_init_stream (data->host, data->port, + login_data->session_id, + login_data->revision_id, + login_data->request_id, dbid, + command, &filesize); + if (! data->channel) { + return FALSE; + } + login_data->request_id++; + + metakey = XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE; + xmms_xform_metadata_set_int (xform, metakey, filesize); + + xmms_xform_private_data_set (xform, data); + + xmms_xform_outdata_type_add (xform, + XMMS_STREAM_TYPE_MIMETYPE, + "application/octet-stream", + XMMS_STREAM_TYPE_END); + + g_slist_foreach (dbid_list, (GFunc) cc_item_record_free, NULL); + g_slist_free (dbid_list); + g_free (command); + + return TRUE; +} + +static void +xmms_daap_destroy (xmms_xform_t *xform) +{ + xmms_daap_data_t *data; + + data = xmms_xform_private_data_get (xform); + + g_io_channel_shutdown (data->channel, TRUE, NULL); + g_io_channel_unref (data->channel); + + g_free (data->host); + g_free (data); +} + +static gint +xmms_daap_read (xmms_xform_t *xform, void *buffer, gint len, xmms_error_t *error) +{ + xmms_daap_data_t *data; + gsize read_bytes = 0; + GIOStatus status; + + data = xmms_xform_private_data_get (xform); + + /* request is performed, header is stripped. now read the data. */ + while (read_bytes == 0) { + status = g_io_channel_read_chars (data->channel, buffer, len, + &read_bytes, NULL); + if (status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR) { + break; + } + } + + return read_bytes; +} + + +static gboolean +xmms_daap_browse (xmms_xform_t *xform, const gchar *url, xmms_error_t *error) +{ + gboolean ret = FALSE; + + if (g_strcasecmp (url, "daap://") == 0) { + + GSList *sl = daap_mdns_get_server_list (); + + for (; sl; sl = g_slist_next (sl)) { + daap_mdns_server_t *mdns_serv; + gchar *str; + + mdns_serv = sl->data; + + str = g_strdup_printf ("%s:%d", mdns_serv->address, + mdns_serv->port); + xmms_xform_browse_add_entry (xform, str, XMMS_XFORM_BROWSE_FLAG_DIR); + g_free (str); + + xmms_xform_browse_add_entry_property_str (xform, "servername", + mdns_serv->server_name); + + xmms_xform_browse_add_entry_property_str (xform, "ip", + mdns_serv->address); + + xmms_xform_browse_add_entry_property_str (xform, "name", + mdns_serv->mdns_hostname); + + xmms_xform_browse_add_entry_property_int (xform, "port", + mdns_serv->port); + + /* TODO implement the machinery to allow for this */ + // val = xmms_object_cmd_value_int_new (mdns_serv->need_auth); + // xmms_xform_browse_add_entry_property (xform, "passworded", val); + // val = xmms_object_cmd_value_int_new (mdns_serv->version); + // xmms_xform_browse_add_entry_property (xform, "version", val); + } + + ret = TRUE; + + g_slist_free (sl); + } else { + gchar *host; + guint port; + + if (get_data_from_url (url, &host, &port, NULL, error)) { + ret = daap_get_urls_from_server (xform, host, port, error); + g_free (host); + } + } + + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/daap/xmms2-daap/wscript Sun Aug 05 00:26:21 2007 +0300 @@ -0,0 +1,36 @@ +from waftools.plugin import plugin +from Params import g_platform + +def plugin_configure(conf): + # Set the default fallthrough, if no "intelligent" backend is found. + conf.env['XMMS_DAAP_BACKEND'] = 'dummy' + + # First look for Avahi mdns support + if (conf.check_pkg("avahi-glib", destvar='avahiglib') and + conf.check_pkg("avahi-client", destvar='avahiclient')): + # Avahi found + conf.env['XMMS_DAAP_BACKEND'] = 'avahi' + elif conf.check_header('dns_sd.h'): + # We might have dnssd support. If we're not on OSX, check for the + # presence of the lib. + if g_platform == 'darwin': + conf.env['XMMS_DAAP_BACKEND'] = 'dnssd' + elif conf.check_library2('dns_sd', uselib='dnssd', mandatory=0): + conf.env['XMMS_DAAP_BACKEND'] = 'dnssd' + return True + +def plugin_build(bld, obj): + daap_backend = bld.env_of_name('default')['XMMS_DAAP_BACKEND'] + obj.source.append("daap_mdns_%s.c" % daap_backend) + if daap_backend == 'avahi': + obj.uselib += ' avahiglib avahiclient' + elif daap_backend == 'dnssd': + obj.uselib += ' dnssd' + if g_platform == 'win32': + obj.uselib += ' socket' + +configure, build = plugin('daap', configure=plugin_configure, + extra_libs=['curl'], build=plugin_build, + source="""daap_xform.c daap_cmd.c daap_conn.c + daap_util.c daap_md5.c cc_handlers.c + """.split())