From: Leandro Lucarella Date: Mon, 29 Oct 2007 19:32:48 +0000 (-0300) Subject: Rewrite the MPD status monitor to use a persistent connection. X-Git-Url: https://git.llucax.com/personal/ion3-config.git/commitdiff_plain/4b127d3c4a082b8ba0b4bea7677222ae753dd575 Rewrite the MPD status monitor to use a persistent connection. Now the MPD status monitor don't create a new connection for each update_interval miliseconds, it uses a persistent netcat connection to get the mpd status. This status monitor is a "merge" between statusd_exec and statusd_mpd. --- diff --git a/default/cfg_statusbar.lua b/default/cfg_statusbar.lua index 5d39b43..d13c2c7 100644 --- a/default/cfg_statusbar.lua +++ b/default/cfg_statusbar.lua @@ -94,8 +94,11 @@ mod_statusbar.launch_statusd{ -- MPD mpd = { + -- netcat path + netcat = 'nc', + -- 500 or less makes seconds increment relatively smoothly while playing - update_interval = 1000, + update_interval = 500, -- mpd server info (localhost:6600 are mpd defaults) address = "localhost", @@ -113,7 +116,8 @@ mod_statusbar.launch_statusd{ -- track metadata: %artist, %title, %num, %album, %year, %len -- current track position: %pos -- escape for the percent character: %% - template = "%title by %artist, track %num from %album, %year (%pos/%len) [%volume]", + template = "%title by %artist, track %num from %album, %year " .. + "(%pos/%len) [%volume]", }, -- Memory monitor diff --git a/mpd_status.sh b/mpd_status.sh deleted file mode 100755 index 8c329ba..0000000 --- a/mpd_status.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python - -from socket import socket -from time import sleep - -s = socket() -s.connect(('localhost', 6600)) - -volume = state = pos = len = '' - -while True: - s.send('status\ncurrentsong\n') - sleep(1) - for line in s.recv(4096).split('\n'): - if line.startswith('volume: '): - volume = line[8:] - if line.startswith('state: '): - state = line[7:] - if line.startswith('time: '): - i = line.rindex(':') - len = line[i+1:] - pos = line[line.index(':')+2:i] - if line.startswith('Artist: '): - artist = line[8:] - if line.startswith('Title: '): - title = line[7:] - if line.startswith('Album: '): - album = line[7:] - if line.startswith('Track: '): - track = line[7:] - if line.startswith('Date: '): - date = line[6:] - if line.startswith('Genre: '): - genre = line[7:] - print volume, state, pos, len, artist, title, album, track, date, genre - diff --git a/statusd_mpd.lua b/statusd_mpd.lua index 57a1103..b2948f2 100644 --- a/statusd_mpd.lua +++ b/statusd_mpd.lua @@ -3,18 +3,19 @@ -- 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 @@ -31,141 +32,146 @@ local defaults={ 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 :