#!/usr/bin/env ruby #A DAAP Server # (c) 2006 Ian Monroe # License: GNU General Public License V2 $LOAD_PATH.push(ARGV[0]) puts "here it is: #{ARGV[0]}" $LOAD_PATH.push(ARGV[1]) puts "here it is: #{ARGV[1]}" require "codes.rb" require 'mongrel' require "#{ARGV[2]}" #debug.rb require 'uri' require 'pp' #require 'ruby-prof' $app_name = "Daap" $debug_prefix = "Server" class Element attr_accessor :name public def initialize(name, value = Array.new) @name, @value = name, value end def to_s( codes = nil ) if @value.nil? then log @name + ' is null' @name + Element.long_convert( 0 ) else content = valueToString( codes ) @name + Element.long_convert(content.length) + content end end def collection? @value.class == Array end def <<( child ) @value << child end def size @value.size end def Element.char_convert( v ) packing( v, 'c' ) end def Element.short_convert( v ) packing( v, 'n' ) end def Element.long_convert( v ) packing( v, 'N' ) end def Element.longlong_convert( v ) v = v.to_i if( v.is_a?(String) ) a = Array.new a[0] = v >> 32 b = Array.new b[0] = v & 0xffffffff a.pack('N') + b.pack('N') end protected def valueToString( codes ) case CODE_TYPE[@name] when :string then @value when :long then Element.long_convert( @value ) when :container then values = String.new @value.each do |i| values += i.to_s( codes ) end values when :char then Element.char_convert( @value ) when :short then Element.short_convert( @value ) when :longlong then Element.longlong_convert( @value ) when :date then Element.long_convert( @value ) when :version then Element.short_convert( @value ) else log "type error! #{@value} #{CODE_TYPE[@name]} #{@name}" end end def Element.packing( v, packer ) v = v.to_i if( v.is_a?(String) ) a = Array.new a[0] = v a.pack(packer) end end class Mlit < Element attr_accessor :songformat, :id def to_s( codes ) values = String.new @value.each { |i| values += i.to_s( codes ) if codes.member?( i.name ) } 'mlit' + Element.long_convert(values.length) + values end end class DatabaseServlet < Mongrel::HttpHandler include DebugMethods public def initItems @@sessionId = 42 artists = Hash.new albums = Hash.new genre = Hash.new year = Hash.new device_paths = Hash.new indexes = [ { :dbresult=> query( 'select * from album' ), :indexed => albums }, { :dbresult=> query( 'select * from artist' ), :indexed => artists }, { :dbresult=> query( 'select * from genre' ) , :indexed => genre }, { :dbresult=> query( 'select * from year' ) , :indexed => year }, { :dbresult=> query( 'select id, lastmountpoint from devices' ), :indexed => device_paths } ] indexes.each { |h| 0.step( h[ :dbresult ].size, 2 ) { |i| h[ :indexed ][ h[ :dbresult ][i].to_i ] = h[ :dbresult ][ i.to_i+1 ] } } columns = [ "album, ", "artist, ", "genre, ", "year, ", "track, ", "title, ", "length, ", "samplerate, ", "url, ", "deviceid" ] puts "SQL QUERY: SELECT #{columns.to_s} FROM tags" @column_keys = [ :songalbum, :songartist, :songgenre, :songyear, :songtracknumber, :itemname, :songtime, :songsamplerate, :url, :deviceid ] #TODO composer :songcomposer @music = Array.new @items = Element.new( 'mlcl' ) id = 0 columnIt = 0 track = Mlit.new( 'mlit' ) url = String.new while ( line = $stdin.gets ) && ( line.chop! != '**** END SQL ****' ) puts "#{columnIt} - #{line}" if id < 10 case columnIt when 0..3 track << Element.new( METAS[ @column_keys[columnIt] ][ :code ], indexes[columnIt][ :indexed ][ line.to_i ] ) when (4 ..( @column_keys.size-3 ) ) track << Element.new( METAS[ @column_keys[columnIt] ][ :code ], line ) when columns.size - 2 url = line.reverse.chop.reverse when columns.size - 1 id += 1 device_id = line.to_i if device_id == -1 then @music[id] = url else url[0] = '' @music[id] = "#{indexes.last[:indexed][ device_id ]}/#{url}" end track << Element.new( 'miid', id ) track << Element.new( 'asfm', File::extname( url ).reverse.chop.reverse ) @items << track columnIt = -1 track = Mlit.new( 'mlit' ) url = String.new end columnIt += 1 end @column_keys.push( :itemid ) @column_keys.push( :songformat ) end debugMethod(:new) def process( request, response ) if @items.nil? then initItems() end uri = URI::parse( request.params["REQUEST_URI"] ) command = File.basename( uri.path ) output = String.new case command #{"mupd"=>{"mstt"=>[200], "musr"=>[2]}} when "login" then root = Element.new( 'mlog' ) root << Element.new( 'mlid', @@sessionId ) root << Element.new( 'mstt', 200 ) #200, as in the HTTP OK code write_resp( response, root.to_s ) @@sessionId += 1 #{"mupd"=>{"mstt"=>[200], "musr"=>[2]}} when "update" then root = Element.new( 'mupd' ) root << Element.new( 'mstt', 200 ) root << Element.new( 'musr', 2 ) write_resp( response, root.to_s ) when 'server-info' # SimpleDaapClient output from Banshee server # {"msrv"=> # {"mpro"=>[131074], # "msbr"=>[nil], # "mslr"=>[nil], # "msup"=>[nil], # "msex"=>[nil], # "msqy"=>[nil], # "msau"=>[nil], # "apro"=>[196610], # "minm"=>["Banshee Music Share"], # "msdc"=>[1], # "mstt"=>[200], # "msal"=>[nil], # "msrs"=>[nil], # "mstm"=>[1800], # "mspi"=>[nil], # "msix"=>[nil]}} msrv = Element.new( 'msrv' ) msrv << Element.new( 'mpro', 0x20002 ) msrv << Element.new( 'msbr', 1 ) msrv << Element.new( 'mslr', 1 ) msrv << Element.new( 'msup', 1 ) msrv << Element.new( 'msex', 1 ) msrv << Element.new( 'msqy', 1 ) msrv << Element.new( 'msau', 0 ) msrv << Element.new( 'apro', 0x30002 ) msrv << Element.new( 'minm', "Amarok Music Share" ) msrv << Element.new( 'msdc', 1 ) msrv << Element.new( 'mstt', 200 ) msrv << Element.new( 'msal', 1 ) msrv << Element.new( 'msrs', 1 ) msrv << Element.new( 'mstm', 1800 ) msrv << Element.new( 'mspi', 1 ) msrv << Element.new( 'msix', 1 ) write_resp( response, msrv.to_s ) when 'content-codes' then write_resp( response, CONTENT_CODES ) #LAAAAAZY when 'containers' then # {"aply"=> # {"muty"=>[nil], # "mstt"=>[200], # "mrco"=>[1], # "mtco"=>[1], # "mlcl"=> # [{"mlit"=> # [{"abpl"=>[nil], # "miid"=>[1], # "mper"=>[0], # "minm"=>["Banshee Music Share"], # "mimc"=>[2463]}]}]}} aply = Element.new( 'aply' ) aply << Element.new( 'muty', nil ) aply << Element.new( 'mstt', 200 ) aply << Element.new( 'mrco', 1 ) mlcl = Element.new( 'mlcl') aply << mlcl mlit = Element.new( 'mlit' ) mlcl << mlit mlit << Element.new( 'abpl', nil ) mlit << Element.new( 'miid', 1 ) mlit << Element.new( 'mper', 0 ) mlit << Element.new( 'minm', "Amarok Music Share" ) mlit << Element.new( 'mimc', @items.size ) write_resp( response, aply.to_s ) when "databases" then # {"avdb"=> # {"muty"=>[nil], # "mstt"=>[200], # "mrco"=>[1], # "mtco"=>[1], # "mlcl"=> # [{"mlit"=> # [{"miid"=>[1], # "mper"=>[0], # "minm"=>["Banshee Music Share"], # "mctc"=>[1], # "mimc"=>[1360]}]}]}} avdb = Element.new( 'avdb' ) avdb << Element.new( 'muty', 0 ) avdb << Element.new( 'mstt', 200 ) avdb << Element.new( 'mrco', 1 ) avdb << Element.new( 'mtco', 1 ) mlcl = Element.new( 'mlcl' ) avdb << mlcl mlit = Element.new( 'mlit' ) mlcl << mlit mlit << Element.new( 'miid', 1 ) mlit << Element.new( 'mper', 0 ) mlit << Element.new( 'minm', ENV['USER'] + " Amarok" ) mlit << Element.new( 'mctc', 1 ) mlit << Element.new( 'mimc', @items.size ) write_resp( response, avdb.to_s ) when "items" then # {"adbs"=> # {"muty"=>[nil], # "mstt"=>[200], # "mrco"=>[1360], # "mtco"=>[1360], # "mlcl"=> # [{"mlit"=> # {"asal"=>["Be Human: Ghost in the Shell"], # "miid"=>[581], # "astm"=>[86000], # "minm"=>["FAX me"], # "astn"=>[nil], # "asar"=>["Yoko Kanno"], # "ascm"=>[""]}, # ... requested = uri.query.nil? ? Array.new : Mongrel::HttpRequest.query_parse( uri.query )['meta'].split(',') puts "#{request.params.inspect} #{requested.inspect} #{uri.to_s} #{request.params["REQUEST_URI"]}" toDisplay = Array.new requested.each { |str| str[0,5] = '' index = str.to_sym if @column_keys.include?( index ) then if( METAS[ index ] ) toDisplay.push( METAS[ index ][:code] ) else log "not being displayed #{index.to_s}" end end } adbs = Element.new( 'adbs' ) adbs << Element.new( 'muty', nil ) adbs << Element.new( 'mstt', 200 ) adbs << Element.new( 'mrco', @items.size ) adbs << Element.new( 'mtco', @items.size ) adbs << @items write_resp( response, adbs.to_s( toDisplay ) ) else if command =~ /([\d]*)\.(.*)$/ #1232.mp3 log "sending #{@music[ $1.to_i ]}" file = @music[ $1.to_i ] response.start(200) do |head,out| response.send_status(File.size( file )) response.header['Content-Type'] = "application/#{$2}" response.send_header response.send_file(file) end response.send_header response.send_file( file ) else response.start( 404 ) do | head, out | out << "Command not implemented." end puts "#{command} not implemented" end end end debugMethod(:do_GET) private def query( sql ) out = Array.new puts "SQL QUERY: #{sql}" while ( line = $stdin.gets) && (line.chop! != '**** END SQL ****' ) out.push( line ) end out end debugMethod(:query) def write_resp( response, value ) response.start do | head, out | head['DAAP-Server'] = 'amarok-kaylee' head['Content-Type'] = 'application/x-dmap-tagged' out << value end end end def log( string ) f = open('/tmp/test.ruby', File::WRONLY | File::APPEND | File::CREAT ) f.puts( string ) f.close end class Controller def initialize port = 3689 no_server = true while no_server begin server = Mongrel::HttpServer.new('0.0.0.0', port) no_server = false rescue Errno::EADDRINUSE if port == 3700 then fatal( "No ports between 3688 and 3700 are open." ) end port += 1 end end ds = DatabaseServlet.new server.register('/', ds ) server.register('daap', ds ) puts "SERVER STARTING: #{port}" server.run.join end end $stdout.sync = true $stderr.sync = true #RubyProf.start Controller.new #result = RubyProf.stop #printer = RubyProf::GraphHtmlPrinter.new(result) #f = open('/tmp/test.html', File::WRONLY | File::CREAT ) #printer.print( f, 3 )