dtas.git  about / heads / tags
duct tape audio suite for *nix
blob 3691ff151ef89e13975b3a04d01fe30424559521 3576 bytes (raw)
$ git show mpd:lib/dtas/mpd_emu_client.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
 
# Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org>
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
# frozen_string_literal: true

# emulate the MPD protocol
require_relative '../dtas'
require 'shellwords'

class DTAS::MpdEmuClient # :nodoc:
  attr_reader :to_io

  # protocol version we support
  SERVER = 'MPD 0.13.0'
  MAX_RBUF = 8192
  ACK = {
    ERROR_NOT_LIST: 1,
    ERROR_ARG: 2,
    ERROR_PASSWORD: 3,
    ERROR_PERMISSION: 4,
    ERROR_UNKNOWN: 5,
    ERROR_NO_EXIST: 50,
    ERROR_PLAYLIST_MAX: 51,
    ERROR_SYSTEM: 52,
    ERROR_PLAYLIST_LOAD: 53,
    ERROR_UPDATE_ALREADY: 54,
    ERROR_PLAYER_SYNC: 55,
    ERROR_EXIST: 56,
  }

  def initialize(io)
    @to_io = io
    @rbuf = ''.b
    @wbuf = nil
    @cmd_listnum = 0
    @cmd_list = nil
    out("OK #{SERVER}\n")
  end

  def dispatch_loop(rbuf)
    while rbuf.sub!(/\A([^\r\n]+)\r?\n/n, '')
      rv = dispatch(Shellwords.split($1))
      next if rv == true
      return rv
    end
    rbuf.size >= MAX_RBUF ? nil : :wait_readable
  end

  def dispatch(argv)
    cmd = argv.shift or return err(:ERROR_UNKNOWN)
    cmd = "mpdcmd_#{cmd}"
    if respond_to?(cmd)
      m = method(cmd)
      params = m.parameters
      rest = params.any? { |x| x[0] == :rest }
      req = params.count { |x| x[0] == :req }
      opt = params.count { |x| x[0] == :opt }
      argc = argv.size
      return err(:ERROR_ARG) if argc < req
      return err(:ERROR_ARG) if !rest && (argc > (req + opt))
      m.call(*argv)
    else
      err(:ERROR_UNKNOWN)
    end
  end

  def unknown_cmd(cmd)
    %Q!#{err(:ERROR_UNKNOWN)} unknown command "#{cmd}"\n!
  end

  def err(sym)
    "[#{ACK[sym]}@#@cmd_listnum {}"
  end

  def mpdcmd_ping; out("OK\n"); end
  def mpdcmd_close(*); nil; end

  def mpdcmd_clearerror
    # player_clear_error
    out("OK\n")
  end

  def mpdcmd_command_list_begin
    if @cmd_list
      @cmd_list << 'command_list_begin' # will trigger failure
    else
      @cmd_list = []
    end
  end

  def mpdcmd_command_list_end
    @cmd_list or return out(unknown_cmd('command_list_end'))
    list = @cmd_list
    @cmd_list = nil
    list.each do |cmd|
      case cmd
      when 'command_list_begin', 'command_list_ok_begin'
        return out(unknown_cmd(cmd))
      end
    end
  end

  def mpdcmd_stats
    out("artists: \n" \
        "albums: \n" \
        "songs: \n" \
        "uptime: \n" \
        "playtime: \n" \
        "db_playtime: \n" \
        "db_update: \n" \
        "OK\n")
  end

  # returns true on complete, :wait_writable when blocked, or nil on error
  def out(buf)
    buf = buf.b
    if @wbuf
      @wbuf << buf
      :wait_writable
    else
      tot = buf.size
      case rv = @to_io.write_nonblock(buf, exception: false)
      when Integer
        return true if rv == tot
        buf.slice!(0, rv).clear
        tot -= rv
      when :wait_writable
        @wbuf = buf.dup
        return rv
      end while tot > 0
      true # all done
    end
  rescue
    nil # signal EOF up the chain
  end

  def dispatch_rd(buf)
    case rv = @to_io.read_nonblock(MAX_RBUF, buf, exception: false)
    when String then dispatch_loop(@rbuf << rv)
    when :wait_readable, nil then rv
    end
  rescue
    nil
  end

  def dispatch_wr
    tot = @wbuf.size
    case rv = @to_io.write_nonblock(@wbuf, exception: false)
    when Integer
      @wbuf.slice!(0, rv).clear # free the written portion
      tot -= rv
      return :wait_readable if tot == 0
    when :wait_writable then return rv
    end while true
  rescue
    nil
  end

  def hash
    @to_io.fileno
  end
end

git clone git://80x24.org/dtas.git
git clone https://80x24.org/dtas.git