aboutsummaryrefslogtreecommitdiffstats
path: root/src/db/ProxyDatabasePlugin.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/db/ProxyDatabasePlugin.cxx')
-rw-r--r--src/db/ProxyDatabasePlugin.cxx320
1 files changed, 222 insertions, 98 deletions
diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
index 00b5d445f..f65e4f3d0 100644
--- a/src/db/ProxyDatabasePlugin.cxx
+++ b/src/db/ProxyDatabasePlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,26 +20,53 @@
#include "config.h"
#include "ProxyDatabasePlugin.hxx"
#include "DatabasePlugin.hxx"
+#include "DatabaseListener.hxx"
#include "DatabaseSelection.hxx"
#include "DatabaseError.hxx"
-#include "PlaylistVector.hxx"
#include "Directory.hxx"
-#include "Song.hxx"
+#include "LightSong.hxx"
#include "SongFilter.hxx"
#include "Compiler.h"
#include "ConfigData.hxx"
#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
#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 <mpd/client.h>
+#include <mpd/async.h>
#include <cassert>
#include <string>
#include <list>
-class ProxyDatabase : public Database {
+class ProxySong : public LightSong {
+ Tag tag2;
+
+public:
+ explicit ProxySong(const mpd_song *song);
+};
+
+class AllocatedProxySong : public ProxySong {
+ mpd_song *const song;
+
+public:
+ explicit AllocatedProxySong(mpd_song *_song)
+ :ProxySong(_song), song(_song) {}
+
+ ~AllocatedProxySong() {
+ mpd_song_free(song);
+ }
+};
+
+class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
+ DatabaseListener &listener;
+
std::string host;
unsigned port;
@@ -49,15 +76,32 @@ 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:
- static Database *Create(const config_param &param,
+ ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener)
+ :SocketMonitor(_loop), IdleMonitor(_loop),
+ listener(_listener) {}
+
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
Error &error);
virtual bool Open(Error &error) override;
virtual void Close() override;
- virtual Song *GetSong(const char *uri_utf8,
+ virtual const LightSong *GetSong(const char *uri_utf8,
Error &error) const override;
- virtual void ReturnSong(Song *song) const;
+ virtual void ReturnSong(const LightSong *song) const;
virtual bool Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -84,6 +128,14 @@ private:
bool Connect(Error &error);
bool CheckConnection(Error &error);
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");
@@ -112,6 +164,38 @@ static constexpr struct {
{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};
+static void
+Copy(TagBuilder &tag, TagType d_tag,
+ const struct mpd_song *song, enum mpd_tag_type s_tag)
+{
+
+ for (unsigned i = 0;; ++i) {
+ const char *value = mpd_song_get_tag(song, s_tag, i);
+ if (value == nullptr)
+ break;
+
+ tag.AddItem(d_tag, value);
+ }
+}
+
+ProxySong::ProxySong(const mpd_song *song)
+{
+ directory = nullptr;
+ uri = mpd_song_get_uri(song);
+ tag = &tag2;
+ mtime = mpd_song_get_last_modified(song);
+ start_ms = mpd_song_get_start(song) * 1000;
+ end_ms = mpd_song_get_end(song) * 1000;
+
+ TagBuilder tag_builder;
+ tag_builder.SetTime(mpd_song_get_duration(song));
+
+ for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
+ Copy(tag_builder, i->d, song, i->s);
+
+ tag_builder.Commit(tag2);
+}
+
gcc_const
static enum mpd_tag_type
Convert(TagType tag_type)
@@ -217,9 +301,10 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
}
Database *
-ProxyDatabase::Create(const config_param &param, Error &error)
+ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error)
{
- ProxyDatabase *db = new ProxyDatabase();
+ ProxyDatabase *db = new ProxyDatabase(loop, listener);
if (!db->Configure(param, error)) {
delete db;
db = nullptr;
@@ -252,10 +337,10 @@ ProxyDatabase::Open(Error &error)
void
ProxyDatabase::Close()
{
- root->Free();
+ delete root;
if (connection != nullptr)
- mpd_connection_free(connection);
+ Disconnect();
}
bool
@@ -269,11 +354,15 @@ 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) {
- mpd_connection_free(connection);
- connection = nullptr;
- }
+ if (connection != nullptr)
+ Disconnect();
return false;
}
@@ -287,10 +376,22 @@ ProxyDatabase::CheckConnection(Error &error)
assert(connection != nullptr);
if (!mpd_connection_clear_error(connection)) {
- mpd_connection_free(connection);
+ Disconnect();
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;
}
@@ -302,10 +403,80 @@ ProxyDatabase::EnsureConnected(Error &error)
: Connect(error);
}
-static Song *
-Convert(const struct mpd_song *song);
+void
+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);
-Song *
+ 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();
+}
+
+const LightSong *
ProxyDatabase::GetSong(const char *uri, Error &error) const
{
// TODO: eliminate the const_cast
@@ -318,43 +489,39 @@ ProxyDatabase::GetSong(const char *uri, Error &error) const
}
struct mpd_song *song = mpd_recv_song(connection);
- Song *song2 = song != nullptr
- ? Convert(song)
- : nullptr;
- if (song != nullptr)
- mpd_song_free(song);
- if (!mpd_response_finish(connection)) {
- if (song2 != nullptr)
- song2->Free();
-
- CheckError(connection, error);
+ if (!mpd_response_finish(connection) &&
+ !CheckError(connection, error)) {
+ if (song != nullptr)
+ mpd_song_free(song);
return nullptr;
}
- if (song2 == nullptr)
+ if (song == nullptr) {
error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri);
+ return nullptr;
+ }
- return song2;
+ return new AllocatedProxySong(song);
}
void
-ProxyDatabase::ReturnSong(Song *song) const
+ProxyDatabase::ReturnSong(const LightSong *_song) const
{
- assert(song != nullptr);
- assert(song->IsInDatabase());
- assert(song->IsDetached());
+ assert(_song != nullptr);
- song->Free();
+ AllocatedProxySong *song = (AllocatedProxySong *)
+ const_cast<LightSong *>(_song);
+ delete song;
}
static bool
-Visit(struct mpd_connection *connection, const char *uri,
+Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error);
static bool
-Visit(struct mpd_connection *connection,
+Visit(struct mpd_connection *connection, Directory &root,
bool recursive, const SongFilter *filter,
const struct mpd_directory *directory,
VisitDirectory visit_directory, VisitSong visit_song,
@@ -362,80 +529,39 @@ Visit(struct mpd_connection *connection,
{
const char *path = mpd_directory_get_path(directory);
- if (visit_directory) {
- Directory *d = Directory::NewGeneric(path, &detached_root);
- bool success = visit_directory(*d, error);
- d->Free();
- if (!success)
- return false;
- }
+ if (visit_directory &&
+ !visit_directory(Directory(path, &root), error))
+ return false;
if (recursive &&
- !Visit(connection, path, recursive, filter,
+ !Visit(connection, root, path, recursive, filter,
visit_directory, visit_song, visit_playlist, error))
return false;
return true;
}
-static void
-Copy(TagBuilder &tag, TagType d_tag,
- const struct mpd_song *song, enum mpd_tag_type s_tag)
-{
-
- for (unsigned i = 0;; ++i) {
- const char *value = mpd_song_get_tag(song, s_tag, i);
- if (value == nullptr)
- break;
-
- tag.AddItem(d_tag, value);
- }
-}
-
-static Song *
-Convert(const struct mpd_song *song)
-{
- Song *s = Song::NewDetached(mpd_song_get_uri(song));
-
- s->mtime = mpd_song_get_last_modified(song);
- s->start_ms = mpd_song_get_start(song) * 1000;
- s->end_ms = mpd_song_get_end(song) * 1000;
-
- TagBuilder tag;
- tag.SetTime(mpd_song_get_duration(song));
-
- for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
- Copy(tag, i->d, song, i->s);
-
- s->tag = tag.Commit();
-
- return s;
-}
-
gcc_pure
static bool
-Match(const SongFilter *filter, const Song &song)
+Match(const SongFilter *filter, const LightSong &song)
{
return filter == nullptr || filter->Match(song);
}
static bool
Visit(const SongFilter *filter,
- const struct mpd_song *song,
+ const mpd_song *_song,
VisitSong visit_song, Error &error)
{
if (!visit_song)
return true;
- Song *s = Convert(song);
- bool success = !Match(filter, *s) || visit_song(*s, error);
- s->Free();
-
- return success;
+ const ProxySong song(_song);
+ return !Match(filter, song) || visit_song(song, error);
}
static bool
-Visit(const struct mpd_playlist *playlist,
+Visit(const struct mpd_playlist *playlist, Directory &root,
VisitPlaylist visit_playlist, Error &error)
{
if (!visit_playlist)
@@ -444,7 +570,7 @@ Visit(const struct mpd_playlist *playlist,
PlaylistInfo p(mpd_playlist_get_path(playlist),
mpd_playlist_get_last_modified(playlist));
- return visit_playlist(p, detached_root, error);
+ return visit_playlist(p, root, error);
}
class ProxyEntity {
@@ -486,7 +612,7 @@ ReceiveEntities(struct mpd_connection *connection)
}
static bool
-Visit(struct mpd_connection *connection, const char *uri,
+Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error)
@@ -504,7 +630,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_DIRECTORY:
- if (!Visit(connection, recursive, filter,
+ if (!Visit(connection, root, recursive, filter,
mpd_entity_get_directory(entity),
visit_directory, visit_song, visit_playlist,
error))
@@ -519,7 +645,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_PLAYLIST:
- if (!Visit(mpd_entity_get_playlist(entity),
+ if (!Visit(mpd_entity_get_playlist(entity), root,
visit_playlist, error))
return false;
break;
@@ -549,12 +675,10 @@ SearchSongs(struct mpd_connection *connection,
bool result = true;
struct mpd_song *song;
while (result && (song = mpd_recv_song(connection)) != nullptr) {
- Song *song2 = Convert(song);
- mpd_song_free(song);
+ AllocatedProxySong song2(song);
- result = !Match(selection.filter, *song2) ||
- visit_song(*song2, error);
- song2->Free();
+ result = !Match(selection.filter, song2) ||
+ visit_song(song2, error);
}
mpd_response_finish(connection);
@@ -578,7 +702,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
return ::SearchSongs(connection, selection, visit_song, error);
/* fall back to recursive walk (slow!) */
- return ::Visit(connection, selection.uri.c_str(),
+ return ::Visit(connection, *root, selection.uri.c_str(),
selection.recursive, selection.filter,
visit_directory, visit_song, visit_playlist,
error);