From 71d012fa61289d167a4337f6f0aa17ac5bf39f12 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 10 Jan 2014 23:44:03 +0100 Subject: [PATCH] db/proxy: forward "idle" events Send "idle" to the other MPD whenever there's nothing else to do and forward incoming "idle database" events to all our MPD clients. --- NEWS | 1 + src/db/ProxyDatabasePlugin.cxx | 119 +++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 2e30adb2c..f735673fa 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ ver 0.19 (not yet released) - new commands "addtagid", "cleartagid" - "lsinfo" and "readcomments" allowed for remote files * database + - proxy: forward "idle" events - upnp: new plugin * playlist - soundcloud: use https instead of http diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx index e8d64003c..7e8dcf65f 100644 --- a/src/db/ProxyDatabasePlugin.cxx +++ b/src/db/ProxyDatabasePlugin.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "ProxyDatabasePlugin.hxx" #include "DatabasePlugin.hxx" +#include "DatabaseListener.hxx" #include "DatabaseSelection.hxx" #include "DatabaseError.hxx" #include "Directory.hxx" @@ -31,14 +32,21 @@ #include "util/Error.hxx" #include "util/Domain.hxx" #include "protocol/Ack.hxx" +#include "Main.hxx" +#include "event/SocketMonitor.hxx" +#include "event/IdleMonitor.hxx" +#include "Log.hxx" #include +#include #include #include #include -class ProxyDatabase : public Database { +class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor { + DatabaseListener &listener; + std::string host; unsigned port; @@ -48,7 +56,23 @@ class ProxyDatabase : public Database { /* this is mutable because GetStats() must be "const" */ mutable time_t update_stamp; + /** + * The libmpdclient idle mask that was removed from the other + * MPD. This will be handled by the next OnIdle() call. + */ + unsigned idle_received; + + /** + * Is the #connection currently "idle"? That is, did we send + * the "idle" command to it? + */ + bool is_idle; + public: + ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener) + :SocketMonitor(_loop), IdleMonitor(_loop), + listener(_listener) {} + static Database *Create(EventLoop &loop, DatabaseListener &listener, const config_param ¶m, Error &error); @@ -86,6 +110,12 @@ private: bool EnsureConnected(Error &error); void Disconnect(); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from IdleMonitor */ + virtual void OnIdle() override; }; static constexpr Domain libmpdclient_domain("libmpdclient"); @@ -219,11 +249,10 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) } Database * -ProxyDatabase::Create(gcc_unused EventLoop &loop, - gcc_unused DatabaseListener &listener, +ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener, const config_param ¶m, Error &error) { - ProxyDatabase *db = new ProxyDatabase(); + ProxyDatabase *db = new ProxyDatabase(loop, listener); if (!db->Configure(param, error)) { delete db; db = nullptr; @@ -273,6 +302,12 @@ ProxyDatabase::Connect(Error &error) return false; } + idle_received = unsigned(-1); + is_idle = false; + + SocketMonitor::Open(mpd_async_get_fd(mpd_connection_get_async(connection))); + IdleMonitor::Schedule(); + if (!CheckError(connection, error)) { if (connection != nullptr) Disconnect(); @@ -293,6 +328,18 @@ ProxyDatabase::CheckConnection(Error &error) return Connect(error); } + if (is_idle) { + unsigned idle = mpd_run_noidle(connection); + if (idle == 0 && !CheckError(connection, error)) { + Disconnect(); + return false; + } + + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + } + return true; } @@ -309,10 +356,74 @@ ProxyDatabase::Disconnect() { assert(connection != nullptr); + IdleMonitor::Cancel(); + SocketMonitor::Steal(); + mpd_connection_free(connection); connection = nullptr; } +bool +ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) +{ + assert(connection != nullptr); + + if (!is_idle) { + // TODO: can this happen? + IdleMonitor::Schedule(); + return false; + } + + unsigned idle = (unsigned)mpd_recv_idle(connection, false); + if (idle == 0) { + Error error; + if (!CheckError(connection, error)) { + LogError(error); + Disconnect(); + return false; + } + } + + /* let OnIdle() handle this */ + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + return false; +} + +void +ProxyDatabase::OnIdle() +{ + assert(connection != nullptr); + + /* handle previous idle events */ + + if (idle_received & MPD_IDLE_DATABASE) + listener.OnDatabaseModified(); + + idle_received = 0; + + /* send a new idle command to the other MPD */ + + if (is_idle) + // TODO: can this happen? + return; + + if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) { + Error error; + if (!CheckError(connection, error)) + LogError(error); + + SocketMonitor::Steal(); + mpd_connection_free(connection); + connection = nullptr; + return; + } + + is_idle = true; + SocketMonitor::ScheduleRead(); +} + static Song * Convert(const struct mpd_song *song);