require "socket" module Rmpd class Connection include Socket::Constants include Rmpd::Commands MAX_RETRIES = 5 attr_reader :socket def initialize(config_file=nil) @config = Rmpd::Config.new(config_file) @socket = nil @socket_mu = Mutex.new end def close @socket_mu.lock @socket.close @socket_mu.unlock end def connect @socket_mu.lock unless @socket.nil? || @socket.closed? @socket_mu.unlock return end @socket_mu.unlock if %r{^/} === @config.hostname connect_unix_socket else connect_inet_socket end read_response # protocol version, ignore for now password(@config.password) if @config.password end def connect_unix_socket @socket_mu.lock begin @socket = UNIXSocket.new(@config.hostname) rescue StandardError => error @socket = nil raise MpdConnRefusedError.new(error) ensure @socket_mu.unlock end end def connect_inet_socket Socket::getaddrinfo(@config.hostname, @config.port, nil, SOCK_STREAM).each do |info| @socket_mu.lock begin sockaddr = Socket.pack_sockaddr_in(info[1], info[3]) @socket = Socket.new(info[4], info[5], 0) @socket.connect(sockaddr) rescue StandardError => error @socket = nil raise MpdConnRefusedError.new(error) else break ensure @socket_mu.unlock end end end def send_command(command, *args) tries = 0 begin connect send_command_without_reconnect(command, *args) rescue Errno::EPIPE, EOFError if (tries += 1) < MAX_RETRIES retry else raise MpdError.new("Retry count exceeded") end end end def send_command_without_reconnect(command, *args) @socket_mu.lock @socket.puts("#{command} #{quote(args).join(" ")}".strip) rescue => e @socket.close raise e ensure @socket_mu.unlock end def read_response response = [] @socket_mu.synchronize do while (line = @socket.readline.force_encoding("UTF-8")) response << line.strip break if END_RE === line end end response end def mpd self end def quote(args) args.collect {|arg| "\"#{arg.to_s.gsub(/"/, "\\\"").gsub(/\\/, "\\\\\\\\")}\""} end end end