#!/usr/local/bin/perl -w use strict; #sub POE::Kernel::ASSERT_DEFAULT () {1} #sub POE::Kernel::TRACE_DEFAULT () {1} use FindBin; use lib "$FindBin::Bin/../POE-Component-Client-DAAP/lib"; use lib "$FindBin::Bin/../POE-Component-Rendezvous/lib"; package Net::DAAP::Proxy; use POE qw(Component::Client::DAAP Component::Rendezvous); use POE::Component::Server::HTTP; # RC_* use POE::Kernel; use base qw(Net::DAAP::Server); use Net::Rendezvous; __PACKAGE__->mk_accessors(qw(daap)); our $id_offset = 1_000_000; # captured from Server process my @want_fields = qw( dmap.itemkind dmap.itemid dmap.itemname dmap.persistentid com.apple.itunes.smart-playlist dmap.haschildcontainers dmap.parentcontainerid daap.songdateadded daap.songdatemodified daap.songdisccount daap.songdiscnumber daap.songdisabled daap.songeqpreset daap.songformat daap.songgenre daap.songdescription daap.songrelativevolume daap.songsamplerate daap.songsize daap.songstarttime daap.songstoptime daap.songtime daap.songtrackcount daap.songtracknumber daap.songuserrating daap.songyear daap.songdatakind daap.songdataurl daap.songcontentrating daap.songartist daap.songalbum daap.songcomment com.apple.itunes.norm-volume com.apple.itunes.itms-songid com.apple.itunes.itms-artistid com.apple.itunes.itms-playlistid com.apple.itunes.itms-composerid com.apple.itunes.itms-genreid com.apple.itunes.itms-storefrontid ); use Data::Dumper; use Sys::Hostname; # copied from Net::DMAP::Server and nuked Rendezvous sub new { my $class = shift; my $self = bless { db_uuid => '13950142391337751523', revision => 42, tracks => {}, playlists => {}, waiting_clients => [], poll_interval => 20, @_ }, $class; $self->name( ref($self) ." " . hostname . " $$" ) unless $self->name; $self->port( $self->default_port ) unless $self->port; $self->find_tracks; $self->httpd( POE::Component::Server::HTTP->new( Port => $self->port, ContentHandler => { '/' => sub { $self->_handler(@_) } }, StreamHandler => sub { $self->_stream_handler(@_) }, ) ); POE::Session->create( inline_states => { _start => sub { $_[KERNEL]->alarm( poll_changed => time + $self->poll_interval ) ; }, poll_changed => sub { $self->poll_changed; $_[KERNEL]->yield('_start'); }, }); return $self; } sub _stream_handler { } sub find_tracks { my $self = shift; POE::Session->create( inline_states => { _start => \&daap_start, connected => \&daap_connected, dbs => \&daap_dbs, songs => \&daap_songs, got_data => \&daap_got_data, test => \&test, add_track => sub { $self->add_track(@_[ARG0..$#_]) }, published => \&published, discovered => \&discovered, entries => \&entries, }, heap => { self => $self, }, ); } sub daap_start { $_[KERNEL]->alias_set('foo'); warn "our session is ".$_[SESSION]->ID."\n"; $_[HEAP]->{bonjour} = POE::Component::Rendezvous->new( debug => 1, rendezvous_options => [ "daap" ], alias => 'bonjour', ); $_[HEAP]->{publish} = POE::Component::Rendezvous->new( debug => 1, alias => 'publish', package => 'Net::Rendezvous::Publish', rendezvous_options => [ ], ); $_[KERNEL]->yield('test'); $_[HEAP]->{bonjour}->yield(discover => { event => 'discovered' }); } sub discovered { warn "discovered\n"; $_[HEAP]->{bonjour}->entries({ event => 'entries', wantarray => 1 }); $_[HEAP]->{publish}->publish({ event => "published" }, name => $_[HEAP]->{self}->name, type => '_daap._tcp', port => $_[HEAP]->{self}->port, txt => "Database ID=".$_[HEAP]->{self}->db_uuid."\x{1}Machine Name=".$_[HEAP]->{self}->name, ); } sub published { warn "published\n"; } sub entries { # warn "entries: ".Data::Dumper->Dump([$_[ARG0]->{result}])."\n"; unless ($_[ARG0]->{result}) { warn "no itunes servers found\n"; return; } for my $share ( @{$_[ARG0]->{result}} ) { my $name; if ($share->name) { $name = $share->name; $name =~ s/\\(\d{3})/chr($1)/eg; # DNS escaped text # $self->name($name . " (DAAP Proxy)"); } $_[HEAP]->{host} = $share->address; $_[HEAP]->{port} = $share->port; $_[HEAP]->{name} = $name; my $alias = join ":", "server", $share->address, $share->port; my $daap = POE::Component::Client::DAAP->new( debug => 1, daap_options => [ SERVER_HOST => $_[HEAP]->{host}, SERVER_PORT => $_[HEAP]->{port}, # PASSWORD => $password SONG_ATTRIBUTES => \@want_fields, ], alias => $alias, ); my $session_id = $daap->session_id(); $_[HEAP]->{"server_$session_id"} = $daap; $_[KERNEL]->post($session_id, connect => { event => 'connected', session_id => $session_id }); warn "finding tracks from $name\n"; } } sub test { warn ".\n"; $_[KERNEL]->delay_set(test => 1); } sub daap_connected { unless ($_[ARG0]->{result}) { warn "connect failed\n"; $_[KERNEL]->post($_[ARG0]->{session_id}, 'shutdown'); return; } $_[KERNEL]->post($_[ARG0]->{session_id}, databases => { event => 'dbs', session_id => $_[ARG0]->{session_id} }); warn "getting databases\n"; } sub daap_dbs { warn "databases: ".Data::Dumper->Dump([$_[ARG0]->{result}])."\n"; $_[KERNEL]->post($_[ARG0]->{session_id}, songs => { event => 'songs', session_id => $_[ARG0]->{session_id} }); warn "getting songs\n"; } sub daap_songs { # warn "songs: ".Data::Dumper->Dump([$_[ARG0]->{result}]) . "\n"; for my $id (keys %{$_[ARG0]->{result}}) { my $new_id = $id + $id_offset * $_[ARG0]->{session_id}; warn "adding $id from $_[ARG0]->{session_id} as $new_id\n"; $_[KERNEL]->yield(add_track => $new_id => $_[ARG0]->{result}->{$id}); } } sub daap_got_data { my $data = $_[ARG0]->{result}; warn "data size ", length($data), " bytes\n"; $_[ARG0]->{response}->send($data); $_[ARG0]->{response}->close; $_[ARG0]->{response}->header('Connection' => 'close'); } sub _stream_handler { } sub add_track { my($self, $id, $data) = @_; my $track = Net::DAAP::Proxy::Track->new_from_daap( $id, $data ) or next; if ($self->tracks->{$id}){ warn "$id dup!\n"; } $self->tracks->{ $id } = $track; } sub database_item { my($self, $request, $response) = @_; my($session_id, $id); { use integer; $session_id = $1 / $id_offset; $id = $1 % $id_offset; } $response->streaming(1); warn "getting $id from $session_id\n"; $poe_kernel->post($session_id => 'get' => { event => 'got_data', session => 'foo', response => $response, }, $id); } package Net::DAAP::Proxy::Track; use base qw( Net::DAAP::Server::Track ); __PACKAGE__->mk_accessors(qw(daap_songcontentrating com_apple_itunes_itms_storefrontid)); sub new_from_daap { my($class, $id, $song) = @_; my $self = $class->SUPER::new; for my $field (keys %$song) { next if $field eq 'dmap.itemid'; (my $method = $field) =~ s{[.-]}{_}g; $self->$method($song->{$field}) if $self->can($method); warn "$method" unless $self->can($method); } $self->dmap_itemid($id); $self; } package main; use POE; my $server = Net::DAAP::Proxy->new( port => 6666, path => "/tmp", ); $server->debug(1); $poe_kernel->run;