Косметические доработки интерфейса.
[mpd-lua.git] / mpd.lua~
diff --git a/mpd.lua~ b/mpd.lua~
new file mode 100755 (executable)
index 0000000..97ac4f4
--- /dev/null
+++ b/mpd.lua~
@@ -0,0 +1,417 @@
+#!/usr/bin/lua
+
+local hasuci,uci = pcall(require,"uci")
+
+require("socket")
+json=require("json")
+
+function url_decode(str)
+  if not str then return nil end
+  str = string.gsub (str, "+", " ")
+  str = string.gsub (str, "%%(%x%x)", function(h) return
+    string.char(tonumber(h,16)) end)
+  str = string.gsub (str, "\r\n", "\n")
+  return str
+end
+
+function split(s, delimiter)
+    local result = {};
+    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
+        result[#result+1] = match;
+    end
+    return result;
+end
+
+function string.starts(String,Start)
+   return string.sub(String,1,string.len(Start))==Start
+end
+
+function process_playlist(playlist)
+  local res={}
+  for _,record in pairs(playlist) do
+    local splitted = split(record,": ")
+    local name = splitted[2]
+    local splitted = split(splitted[1],":")
+    local id = splitted[1]
+    local rectype = splitted[2]
+    local rec = {}
+    if not (id == "OK") then
+      rec["id"] = id
+      rec["type"] = rectype
+      rec["name"] = name
+      res[id] = rec
+    end
+  end
+  return res
+end
+
+function process_playlists(playlists)
+  local res={}
+  for _,record in pairs(playlists) do
+    local splitted = split(record,": ")
+    if splitted[1]=="playlist" then
+      res[#res+1] = splitted[2]
+    end
+  end
+  return res
+end
+
+function process_directory(directory)
+  local res={}
+  for _,record in pairs(directory) do
+    local splitted = split(record,": ")
+    if splitted[1]=="directory" or splitted[1]=="file" then
+      local rec={}
+      rec["type"] = splitted[1]
+      rec["name"] = splitted[2]
+      res[#res+1] = rec
+    end
+  end
+  return res
+end
+
+function mpd_new(settings)
+    local client = {}
+    if settings == nil then settings = {} end
+
+    client.hostname = settings.hostname or "localhost"
+    client.port     = settings.port or 6600
+    client.desc     = settings.desc or client.hostname
+    client.password = settings.password
+    client.timeout  = settings.timeout or 1
+    client.retry    = settings.retry or 60
+
+    return client
+end
+
+function mpd_send(mpd,action,raw)
+
+    local command = string.format("%s\n", action)
+    local values = {}
+
+    -- connect to MPD server if not already done.
+    if not mpd.connected then
+        local now = os.time();
+        if not mpd.last_try or (now - mpd.last_try) > mpd.retry then
+            mpd.socket = socket.tcp()
+            mpd.socket:settimeout(mpd.timeout, 't')
+            mpd.last_try = os.time()
+            mpd.connected = mpd.socket:connect(mpd.hostname, mpd.port)
+            if not mpd.connected then
+                return { errormsg = "could not connect" }
+            end
+            mpd.last_error = nil
+
+            -- Read the server's hello message
+            local line = mpd.socket:receive("*l")
+            if not line:match("^OK MPD") then -- Invalid hello message?
+                mpd.connected = false
+                return { errormsg = string.format("invalid hello message: %s", line) }
+            else
+                _, _, mpd.version = string.find(line, "^OK MPD ([0-9.]+)")
+            end
+
+            -- send the password if needed
+            if mpd.password then
+                local rsp = mpd_send(mpd,string.format("password %s", mpd.password))
+                if rsp.errormsg then
+                    return rsp
+                end
+            end
+        else
+            local retry_sec = mpd.retry - (now - mpd.last_try)
+            return { errormsg = string.format("%s (retrying in %d sec)", mpd.last_error, retry_sec) }
+        end
+    end
+
+    mpd.socket:send(command)
+
+    local line = ""; err=0
+    while not line:match("^OK$") do
+        line, err = mpd.socket:receive("*l")
+        if not line then -- closed,timeout (mpd killed?)
+            mpd.last_error = err
+            mpd.connected = false
+            mpd.socket:close()
+            return mpd_send(mpd,action)
+        end
+
+        if line:match("^ACK") then
+            return { errormsg = line }
+        end
+
+        if not raw then
+
+         local pattern = string.format("(%s)", ": ")
+         local i = string.find (line, pattern, 0)
+
+          if i ~= nil then
+              local key=string.sub(line,1,i-1)
+              local value=string.sub(line,i+2,-1)
+              values[string.lower(key)] = value
+          end
+
+       else
+
+         values[#values+1]=line
+
+       end
+    end
+
+    return values
+end
+
+if hasuci then
+
+  x = uci.cursor()
+
+  settings = {}
+  settings['host'] = x.get("mpd","server","host") or "localhost"
+  settings['port'] = x.get("mpd","server","port") or 6600
+  settings['timeout'] = x.get("mpd","server","timeout") or 1
+
+  volstep = x.get("mpd","control","volume_step") or 3
+
+  password = x.get("mpd","server","password")
+
+else
+
+  config = arg[1]
+  if not config then
+    config="/etc/mpd-lua.json"
+  end
+
+  settings={}
+  local open = io.open
+  local file = open(config, "r")
+  if file then
+    local content = file:read "*a"
+    file:close()
+    settings=json.decode(content)
+  end
+    
+  settings['host'] = settings['host'] or "localhost"
+  settings['port'] = settings["port"] or 6600
+  settings['timeout'] = settings["timeout"] or 1
+
+  volstep = settings["volstep"] or 3
+
+end
+  
+  
+if password then
+  settings["password"] = password
+end
+
+m = mpd_new(settings)
+
+command = url_decode(os.getenv('QUERY_STRING'))
+
+if not command or command=="" then
+  command="idle"
+end
+
+if command=="play" or command=="pause" or command=="stop" then
+
+  res=mpd_send(m,command)
+
+elseif command=="previous" or command=="next" then
+
+  res=mpd_send(m,"play")
+  res=mpd_send(m,command)
+
+elseif command=="idle" then
+
+  m.timeout=30
+  res=mpd_send(m,command)
+
+elseif command=="vold" then
+
+  status=mpd_send(m,"status")
+  volume=tonumber(status["volume"])
+  res=mpd_send(m,"setvol "..(volume-volstep))
+
+elseif command=="volu" then
+
+  status=mpd_send(m,"status")
+  volume=tonumber(status["volume"])
+  res=mpd_send(m,"setvol "..(volume+volstep))
+
+elseif string.starts(command,"fastfwd") then
+
+  cmd=split(command,"|")
+  skip=tonumber(cmd[2])
+  if not skip then
+    skip=15
+  end
+
+  status=mpd_send(m,"status")
+  rec_time=status["time"]
+  song=status["song"]
+  
+  if song then
+
+    if rec_time then
+      rec_time=split(rec_time,":")
+      cur_time=tonumber(rec_time[1])
+
+      track_time=tonumber(rec_time[2])
+      cur_time=cur_time+skip
+      if cur_time>track_time then
+        cur_time=track_time
+      end
+
+      mpd_send(m,"seek "..song.." "..cur_time)
+
+    else
+
+      mpd_send(m,"play")
+
+    end  
+  
+  end
+
+  res={}
+
+elseif string.starts(command,"rewind") then
+
+  cmd=split(command,"|")
+  skip=tonumber(cmd[2])
+  if not skip then
+    skip=15
+  end
+
+  status=mpd_send(m,"status")
+  rec_time=status["time"]
+  song=status["song"]
+  
+  if song then
+
+    if rec_time then
+      rec_time=split(rec_time,":")
+      cur_time=tonumber(rec_time[1])
+
+      track_time=tonumber(rec_time[2])
+      cur_time=cur_time-skip
+      if cur_time<0 then
+        cur_time=0
+      end
+
+      mpd_send(m,"seek "..song.." "..cur_time)
+
+    else
+
+      mpd_send(m,"play")
+      mpd_send(m,"previous")
+
+    end  
+  
+  end
+
+  res={}
+
+elseif command=="status" then
+
+  res=mpd_send(m,"status")
+  song=res["song"]
+  playlist=mpd_send(m,"playlist",1)
+  pl=process_playlist(playlist)
+
+  if song then 
+    res['current_playing']=pl[song]['name']
+  else
+    res['song']="--"
+    res['current_playing']="---"
+  end
+
+elseif command=="playlist" then
+
+  playlist=mpd_send(m,"playlist",1)
+  res=process_playlist(playlist)
+
+elseif command=="repeat" then
+
+  status=mpd_send(m,"status")
+  rep=1-status["repeat"]
+  res=mpd_send(m,"repeat "..rep)
+
+elseif string.starts(command,"cpl") then
+
+  cmd=split(command,"|")
+  id=cmd[3]
+  command=cmd[2]
+
+  if command=="playitem" then
+    command="play "..id
+    res=mpd_send(m,command)
+  end
+
+  if command=="clear" then
+    res=mpd_send(m,"clear")
+  end
+
+  if command=="remove" then
+    command="delete "..id
+    res=mpd_send(m,command)
+  end
+
+  if command=="moveup" then
+    command="swap "..id.." "..(id-1)
+    res=mpd_send(m,command)
+  end
+
+  if command=="movedown" then
+    command="swap "..id.." "..(id+1)
+    res=mpd_send(m,command)
+  end
+
+elseif string.starts(command,"lists") then
+
+  cmd=split(command,"|")
+  command=cmd[2]
+
+  if command=="load" then
+    if not cmd[3] then
+      lists=mpd_send(m,"listplaylists",1)
+      res=process_playlists(lists)
+    else
+      res=mpd_send(m,"load "..cmd[3],1)
+    end
+  end
+
+  if command=="save" and cmd[3] then
+    res=mpd_send(m,"save "..cmd[3],1)
+  end
+
+  if command=="delete" and cmd[3] then
+    res=mpd_send(m,"rm "..cmd[3],1)
+  end
+
+  if command=="edit" then
+    if cmd[3] then
+      dir=mpd_send(m,"lsinfo \""..cmd[3].."\"",1)
+    else
+      dir=mpd_send(m,"lsinfo",1)
+    end
+    res=process_directory(dir)
+  end
+
+  if command=="add" then
+    res={}
+    if cmd[3] then
+      res=mpd_send(m,"add \""..cmd[3].."\"")
+    end
+  end
+
+end
+
+if not res then
+  print("Content-Type: text/plain\r\n")
+  print("MPD server - unknown command "..command)
+elseif (res['error_msg']) then
+  print("Content-Type: text/plain\r\n")
+  print("MPD server connection error: "..res['error_msg'])
+else
+  print "Content-Type: text/plain\r\n"
+  print(json.encode(res))
+end