#!/usr/bin/perl -w # pclink.pl version 0.5a # Written by Nathan Peterson use IO::Socket; use IO::Select; # Note these global variables should be edited to your configuration $global_myname = "Nathan's MP3s"; $global_mp3dir = "/home/nathan/public_html/music"; $global_mp3url = "http://:80/~nathan/music"; # $global_myname # - this is the pclink server name that will show up on your streamium # $global_mp3dir # - this is the base dir were the mp3s are on your harddrive # - make sure to configure this and the mp3url variable if you # - are using the dir() option in your nodesfile # $global_mp3url # - this is the url pointing to the same dir as $global_mp3dir. # - note that if you use the string "" in the url it will be replaced # - later by your actual IP as seen by the streamium # Read in nodesfile open NODES, "){ if(/^\#/ || $_ eq "\n"){ next; } # ignore commented or blank lines ($node,$name,$links) = split /;/; $nodes[$node] = "$name;$links"; chomp($nodes[$node]); $maxnode = ($node > $maxnode ? $node : $maxnode); } close NODES; # put mp3 file urls in nodes array $n = $maxnode; for($i=0;$i<=$maxnode;$i++){ next if (!defined($nodes[$i]) || $nodes[$i] !~ /\;dir\(/); $nodes[$i] =~ /^(.*);dir\((.*)\)/; $name = $1; $dir = $2; $nodes[$i] = "$name;"; opendir DIR, $global_mp3dir."/".$dir; @files = sort (grep !/^\.\.?\z/, readdir DIR); closedir DIR; foreach $name (@files){ $n++; $url = $global_mp3url."/$dir/$name"; $url =~ s/ /%20/g; # streamium doesn't like spaces $nodes[$n] = "$name;$url"; $nodes[$i] = $nodes[$i].$n.","; } if($nodes[$i] =~ /,$/){ chop($nodes[$i]); } } # Will need for later $sock_sel = new IO::Select(); # Open UDP sock for listening $udpsock = new IO::Socket::INET (LocalPort => 42591, Proto => 'udp' ); die "Could not connect: $!" unless $udpsock; RESTART: # jump back to here if we need to restart the server # Wait for UDP broadcast $udpsock->recv($datagram, 4096); $clientIP = $udpsock->peerhost(); print "$datagram\n\n"; # start over if not pclink client if($datagram !~ /^/){ goto(RESTART); } # Open tcpsock connection $hellosock = new IO::Socket::INET (PeerAddr => $clientIP, PeerPort => 42951, Proto => 'tcp' ); die "Socket could not be created. Reason: $!\n" unless $hellosock; # record my IP address for later $myIP = $hellosock->sockhost(); # Send Hello, close connection &hello_resp($hellosock,$global_myname); close ($hellosock); # Open tcpsock for listening $pclinksock = new IO::Socket::INET (LocalPort => 42951, Proto => 'tcp', Listen => 1, Reuse => 1 ); die "Could not connect: $!" unless $pclinksock; $sock_sel->add($udpsock); $sock_sel->add($pclinksock); while(1){ # get a set of readable handles (blocks until at least one handle is ready) # take all readable handles in turn @ready = $sock_sel->can_read(); foreach $rsock (@ready) { # if it is pclinksock then we should accept(), read, and respond if($rsock == $pclinksock){ $connection = $pclinksock->accept(); ($node,$elem,$index) = &get_node($connection); $data = &make_xml($node,$elem,$index); &pclink_send($connection, $data); close($connection); } # if it is udpsock then client has reset so we must close tcp sock and restart server # note that it highly unlikely that some non-pclink client is broadcasting on this port, so we will take our chances. elsif($rsock == $udpsock){ $sock_sel->remove($udpsock); $sock_sel->remove($pclinksock); close($pclinksock); goto(RESTART); } # otherwise wtf?!? else { die "unknown handle: $rsock"; } } } ################ ## Subroutines ################ sub hello_resp { my ($sock,$name) = @_; my ($IP) = $sock->sockhost(); my (@IP) = split /\./,$IP; # convert IP address to little endian $IP = $IP[0] + $IP[1]*0x100 + $IP[2]*0x10000 + $IP[3]*0x1000000; my ($hello) = "1.0MUSICMATCH$name$name$IP51111\n"; print $sock $hello; print $hello; $sock->flush(); # is this necessary? } sub pclink_send { my ($sock,$data) = @_; my ($datalen) = length $data; my ($header) = "HTTP/1.0 200 OK\r\nAccept-Ranges: bytes\r\nContent-Length:$datalen\r\nContent-Type: text/xml\r\n\r\n"; print $sock $header.$data; print $header.$data; $sock->flush(); # is this necessary? } sub get_node { my ($sock) = @_; my ($datagram,$nodeid,$numelem,$fromindex); $sock->recv($datagram, 4096); print "\n\n$datagram\n"; $nodeid = ($datagram =~ /(.*)<\/nodeid>/ ? $1 : 0); $numelem = ($datagram =~ /(.*)<\/numelem>/ ? $1 : 0); $fromindex = ($datagram =~ /(.*)<\/fromindex>/ ? $1 : 0); return ($nodeid,$numelem,$fromindex); } sub make_xml { my ($node,$elem,$index) = @_; my ($nodeline) = $nodes[$node]; my ($links) = (split /;/, $nodeline)[1]; my (@links) = split /,/, $links; my ($totnumelem) = scalar @links; my ($xml,$i,$name,$url,$len,@files); $xml = ""; $numelem = $totnumelem; for($i=$index;$i<$numelem;$i++){ $nodeline = $nodes[$links[$i]]; ($name,$_) = split /;/, $nodeline; if(/^http:/){ s/http:\/\//http:\/\/$myIP/; $url = (split /,/)[0]; $url =~ s/ /%20/g; # streamium doesn't like spaces $len = /length\((.*)\)/ ? $1 : ""; $xml = $xml."$name$links[$i]$url"; $xml = $xml."$name"; $xml = $xml."$len"; } else{ $xml = $xml."$name$links[$i]"; } } $xml = $xml."$totnumelem0$numelem\n"; return $xml; }