-- requires that netcat is available in the path
-NETCAT='nc'
-
local defaults={
+ -- netcat path
+ netcat = 'netcat',
+
-- 500 or less makes seconds increment relatively smoothly while playing
- update_interval=500,
+ update_interval = 1000,
-- mpd server info (localhost:6600 are mpd defaults)
- address="localhost",
- port=6600,
+ address = "localhost",
+ port = 6600,
-- mpd password (if any)
- password=nil,
+ password = nil,
-- seconds to consider the mpd in 'important' hint (0 never, -1 always)
important_time = 10, -- seconds
template = "%artist - %num - %title (%pos / %len)"
}
-local settings=table.join(statusd.get_config("mpd"), defaults)
+local settings = table.join(statusd.get_config("mpd"), defaults)
-local success
-local last_success
+local timer
local last_volume
local last_state
-local function get_mpd_status()
- local cmd_string = "status\ncurrentsong\nclose\n"
- if settings.password ~= nil then
- cmd_string = "password " .. settings.password .. "\n" .. cmd_string
- end
- cmd_string = string.format('echo -n "%s" | '..NETCAT..' %s %d',
- cmd_string, settings.address, settings.port)
+-- Function for starting and restarting execution of a process
- last_success = success
- success = false
+local function start_execution()
- local mpd = io.popen(cmd_string, "r")
+ -- Set up a function for receiving the data
+ -- and informing the statusbar
- -- welcome msg
- local data = mpd:read()
- if data == nil or string.sub(data,1,6) ~= "OK MPD" then
- mpd:close()
- statusd.inform("mpd_hint", "critical")
- return "mpd not running"
- end
+ local function process_input(data)
- -- 'password' response (if necessary)
- if settings.password ~= nil then
- repeat
- data = mpd:read()
- until data == nil or string.sub(data,1,2) == "OK"
- or string.sub(data,1,3) == "ACK"
- if data == nil or string.sub(data,1,2) ~= "OK" then
- mpd:close()
- statusd.inform("mpd_hint", "critical")
- return "bad mpd password"
- end
- end
+ -- At first, we are not initialized
+ local inited = false
+
+ -- When we get new data, output the last complete line
+
+ while data do
- local info = {}
- local hint = "normal"
-
- -- 'status' response
- repeat
- data = mpd:read()
- if data == nil then break end
-
- local _,_,attrib,val = string.find(data, "(.-): (.*)")
- if attrib == "time" then
- _,_,info.pos,info.len = string.find(val, "(%d+):(%d+)")
- if settings.important_time == -1
- or info.pos+0 <= settings.important_time
- and settings.important_time ~= 0 then
- hint = "important"
+ if not inited then
+ -- welcome msg
+ if string.sub(data, 1, 6) ~= "OK MPD" then
+ statusd.inform("mpd_hint", "critical")
+ statusd.inform("mpd", "bad mpd response on init")
+ end
+
+ -- 'password' response (if necessary)
+ if settings.password ~= nil then
+ if string.sub(data, 1, 2) ~= "OK" then
+ statusd.inform("mpd_hint", "critical")
+ statusd.inform("mpd_hint", "bad mpd password")
+ end
+ end
+
+ -- everything ok, receive more data
+ inited = true
+ data = coroutine.yield()
end
- info.pos = string.format("%d:%02d", math.floor(info.pos / 60),
- math.mod(info.pos, 60))
- info.len = string.format("%d:%02d", math.floor(info.len / 60),
- math.mod(info.len, 60))
- elseif attrib == "state" then
- info.state = val
- if info.state ~= last_state then
- hint = "important"
+
+ local info = {}
+ local hint = "normal"
+
+ for attrib, val in string.gfind(data, "([^\n]-): ([^\n]*)") do
+
+ if attrib == "time" then
+ _, _, info.pos, info.len = string.find(val, "(%d+):(%d+)")
+ if settings.important_time == -1
+ or info.pos+0 <= settings.important_time
+ and settings.important_time ~= 0 then
+ hint = "important"
+ end
+ info.pos = string.format("%d:%02d", math.floor(info.pos / 60),
+ math.mod(info.pos, 60))
+ info.len = string.format("%d:%02d", math.floor(info.len / 60),
+ math.mod(info.len, 60))
+ elseif attrib == "state" then
+ info.state = val
+ if info.state ~= last_state then
+ hint = "important"
+ end
+ last_state = info.state
+ elseif attrib == "volume" then
+ info.volume = val .. "%"
+ if info.volume ~= last_volume then
+ hint = "important"
+ end
+ last_volume = info.volume
+ elseif attrib == "Artist" then
+ info.artist = val
+ elseif attrib == "Title" then
+ info.title = val
+ elseif attrib == "Album" then
+ info.album = val
+ elseif attrib == "Track" then
+ info.num = val
+ elseif attrib == "Date" then
+ info.year = val
+ end
end
- last_state = info.state
- elseif attrib == "volume" then
- info.volume = val .. "%"
- if info.volume ~= last_volume then
- hint = "important"
+
+ -- done querying; now build the string
+ statusd.inform("mpd_hint", hint)
+ if info.state == "play" then
+ local mpd_st = settings.template
+ -- fill in %values
+ mpd_st = string.gsub(mpd_st, "%%([%w%_]+)",
+ function (x) return(info[x] or "") end)
+ mpd_st = string.gsub(mpd_st, "%%%%", "%%")
+ statusd.inform("mpd", mpd_st)
+ elseif info.state == "pause" then
+ statusd.inform("mpd", "Paused")
+ else
+ statusd.inform("mpd", "No song playing")
end
- last_volume = info.volume
- end
- until string.sub(data,1,2) == "OK" or string.sub(data,1,3) == "ACK"
- if data == nil or string.sub(data,1,2) ~= "OK" then
- mpd:close()
- statusd.inform("mpd_hint", "critical")
- return "error querying mpd status"
- end
- -- 'currentsong' response
- repeat
- data = mpd:read()
- if data == nil then break end
-
- local _,_,attrib,val = string.find(data, "(.-): (.*)")
- if attrib == "Artist" then info.artist = val
- elseif attrib == "Title" then info.title = val
- elseif attrib == "Album" then info.album = val
- elseif attrib == "Track" then info.num = val
- elseif attrib == "Date" then info.year = val
+ -- Wait for bgread to signal that more data
+ -- is available or the program has exited
+
+ data = coroutine.yield()
end
- until string.sub(data,1,2) == "OK" or string.sub(data,1,3) == "ACK"
- if data == nil or string.sub(data,1,2) ~= "OK" then
- mpd:close()
- statusd.inform("mpd_hint", "critical")
- return "error querying current song"
- end
- mpd:close()
-
- success = true
-
- -- done querying; now build the string
- statusd.inform("mpd_hint", hint)
- if info.state == "play" then
- local mpd_st = settings.template
- -- fill in %values
- mpd_st = string.gsub(mpd_st, "%%([%w%_]+)",
- function (x) return(info[x] or "") end)
- mpd_st = string.gsub(mpd_st, "%%%%", "%%")
- return mpd_st
- elseif info.state == "pause" then
- return "Paused"
- else
- return "No song playing"
+ -- Program has exited.
+ -- Start a timer for a new execution if set.
+
+ timer:set(settings.update_interval,
+ function() start_execution() end)
end
-end
+ -- Set up a simple function for printing errors
-local mpd_timer
+ local function process_error(data)
+ while data do
+ io.stderr:write("ion-statusd [statusd_mpd]: " .. data)
+ io.stderr:flush()
+ data = coroutine.yield()
+ end
+ end
-local function update_mpd()
- -- update unless there's an error that's not yet twice in a row, to allow
- -- for transient errors due to load spikes
- local mpd_st = get_mpd_status()
- if success or not last_success then
- statusd.inform("mpd", mpd_st)
+ -- Execute the program in the background and create
+ -- coroutine functions to handle input and error data
+ local sleep = settings.update_interval / 1000
+ sleep = string.gsub(sleep, '(%d+),(%d+)', '%1.%2')
+ local cmd = 'while sleep ' .. sleep .. '; do ' ..
+ 'echo "status\ncurrentsong"; done | ' ..
+ settings.netcat .. ' ' .. settings.address .. ' ' ..
+ settings.port
+ if settings.password ~= nil then
+ cmd = "echo 'password " .. settings.password .. "'; " .. cmd
end
- mpd_timer:set(settings.update_interval, update_mpd)
+ statusd.popen_bgread(cmd, coroutine.wrap(process_input),
+ coroutine.wrap(process_error))
+
end
--- Init
-mpd_timer=statusd.create_timer()
-update_mpd()
+-- Now start execution of all defined processes
+
+timer = statusd.create_timer()
+start_execution()
-- vim: set et sts=4 sw=4 :