diff options
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | src/DatabaseRegistry.cxx | 5 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.cxx | 345 | ||||
-rw-r--r-- | src/db/ProxyDatabasePlugin.hxx | 27 |
5 files changed, 400 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index ec55c1bb6..4e84dc30e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,6 +20,7 @@ noinst_LIBRARIES = \ liboutput_plugins.a src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ + $(LIBMPDCLIENT_CFLAGS) \ $(AVAHI_CFLAGS) \ $(LIBWRAP_CFLAGS) \ $(SQLITE_CFLAGS) @@ -437,7 +438,13 @@ libdb_plugins_a_SOURCES = \ src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \ src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx +if HAVE_LIBMPDCLIENT +libdb_plugins_a_SOURCES += \ + src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx +endif + DB_LIBS = \ + $(LIBMPDCLIENT_LIBS) \ libdb_plugins.a # archive plugins @@ -1043,7 +1050,7 @@ test_run_resolver_SOURCES = test/run_resolver.c \ src/resolver.c test_DumpDatabase_LDADD = \ - libdb_plugins.a \ + $(DB_LIBS) \ libutil.a \ $(GLIB_LIBS) test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ diff --git a/configure.ac b/configure.ac index 3c3add129..358b674af 100644 --- a/configure.ac +++ b/configure.ac @@ -144,6 +144,12 @@ AC_CHECK_HEADERS(valgrind/memcheck.h) dnl --------------------------------------------------------------------------- dnl Allow tools to be specifically built dnl --------------------------------------------------------------------------- + +AC_ARG_ENABLE(mpdclient, + AS_HELP_STRING([--enable-libmpdclient], + [enable support for the MPD client]),, + enable_libmpdclient=auto) + AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) @@ -534,6 +540,15 @@ dnl --------------------------------------------------------------------------- dnl Miscellaneous Libraries dnl --------------------------------------------------------------------------- +dnl -------------------------------- libmpdclient -------------------------------- +MPD_AUTO_PKG(libmpdclient, LIBMPDCLIENT, [libmpdclient >= 2.2], + [MPD client library], [libmpdclient not found]) +if test x$enable_libmpdclient = xyes; then + AC_DEFINE(HAVE_LIBMPDCLIENT, 1, [Define to use libmpdclient]) +fi + +AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes) + dnl --------------------------------- inotify --------------------------------- AC_CHECK_FUNCS(inotify_init inotify_init1) diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx index c9189f733..cf01decdd 100644 --- a/src/DatabaseRegistry.cxx +++ b/src/DatabaseRegistry.cxx @@ -17,13 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "DatabaseRegistry.hxx" #include "db/SimpleDatabasePlugin.hxx" +#include "db/ProxyDatabasePlugin.hxx" #include <string.h> const DatabasePlugin *const database_plugins[] = { &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif NULL }; diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..3b2fb36a2 --- /dev/null +++ b/src/db/ProxyDatabasePlugin.cxx @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyDatabasePlugin.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "gcc.h" + +extern "C" { +#include "db_error.h" +#include "db_visitor.h" +#include "db_save.h" +#include "db_lock.h" +#include "conf.h" +#include "song.h" +#include "tag.h" +} + +#include "directory.h" +#include "playlist_vector.h" + +#undef MPD_DIRECTORY_H +#undef MPD_SONG_H +#include <mpd/client.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxyDatabase : public Database { + std::string host; + unsigned port; + + struct mpd_connection *connection; + +public: + static Database *Create(const struct config_param *param, + GError **error_r); + + virtual bool Open(GError **error_r) override; + virtual void Close() override; + virtual struct song *GetSong(const char *uri_utf8, + GError **error_r) const override; + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const override; + +protected: + bool Configure(const struct config_param *param, GError **error_r); +}; + +G_GNUC_CONST +static inline GQuark +libmpdclient_quark(void) +{ + return g_quark_from_static_string("libmpdclient"); +} + +static bool +CheckError(const struct mpd_connection *connection, GError **error_r) +{ + const auto error = mpd_connection_get_error(connection); + if (error == MPD_ERROR_SUCCESS) + return true; + + g_set_error_literal(error_r, libmpdclient_quark(), (int)error, + mpd_connection_get_error_message(connection)); + return false; +} + +Database * +ProxyDatabase::Create(const struct config_param *param, GError **error_r) +{ + ProxyDatabase *db = new ProxyDatabase(); + if (!db->Configure(param, error_r)) { + delete db; + db = NULL; + } + + return db; +} + +bool +ProxyDatabase::Configure(const struct config_param *param, GError **) +{ + host = config_get_block_string(param, "host", ""); + port = config_get_block_unsigned(param, "port", 0); + + return true; +} + +bool +ProxyDatabase::Open(GError **error_r) +{ + connection = mpd_connection_new(host.empty() ? NULL : host.c_str(), + port, 0); + if (connection == NULL) { + g_set_error_literal(error_r, libmpdclient_quark(), + (int)MPD_ERROR_OOM, "Out of memory"); + return false; + } + + if (!CheckError(connection, error_r)) { + mpd_connection_free(connection); + return false; + } + + return true; +} + +void +ProxyDatabase::Close() +{ + assert(connection != nullptr); + + mpd_connection_free(connection); +} + +struct song * +ProxyDatabase::GetSong(const char *uri, GError **error_r) const +{ + // TODO: implement + // TODO: auto-reconnect + + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; +} + +static bool +Visit(struct mpd_connection *connection, directory &parent, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r); + +static bool +Visit(struct mpd_connection *connection, directory &parent, + bool recursive, const struct mpd_directory *directory, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!recursive && !visit_directory) + return true; + + struct directory *d = + directory_new(mpd_directory_get_path(directory), &parent); + + bool success = (!visit_directory || visit_directory(*d, error_r)) && + Visit(connection, *d, recursive, + visit_directory, visit_song, visit_playlist, error_r); + directory_free(d); + + return success; +} + +static void +Copy(struct tag *tag, enum tag_type 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 == NULL) + break; + + tag_add_item(tag, d_tag, value); + } +} + +static song * +Convert(struct directory &parent, const struct mpd_song *song) +{ + char *name = g_path_get_basename(mpd_song_get_uri(song)); + struct song *s = song_file_new(name, &parent); + g_free(name); + + 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; + + struct tag *tag = tag_new(); + tag->time = mpd_song_get_duration(song); + + static constexpr struct { + enum tag_type d; + enum mpd_tag_type s; + } table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } + }; + + tag_begin_add(tag); + for (auto i = table; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag, i->d, song, i->s); + tag_end_add(tag); + + s->tag = tag; + + return s; +} + +static bool +Visit(struct directory &parent, const struct mpd_song *song, + VisitSong visit_song, GError **error_r) +{ + if (!visit_song) + return true; + + struct song *s = Convert(parent, song); + bool success = visit_song(*s, error_r); + song_free(s); + + return success; +} + +static bool +Visit(struct directory &parent, const struct mpd_playlist *playlist, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!visit_playlist) + return true; + + char *name = g_path_get_basename(mpd_playlist_get_path(playlist)); + struct playlist_metadata p; + p.name = name; + p.mtime = mpd_playlist_get_last_modified(playlist); + + bool success = visit_playlist(p, parent, error_r); + g_free(name); + + return success; +} + +static std::list<struct mpd_entity *> +ReceiveEntities(struct mpd_connection *connection) +{ + std::list<struct mpd_entity *> entities; + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(connection)) != NULL) + entities.push_back(entity); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, struct directory &parent, + bool recursive, VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, GError **error_r) +{ + if (!mpd_send_list_meta(connection, directory_get_path(&parent))) + return CheckError(connection, error_r); + + std::list<struct mpd_entity *> entities = ReceiveEntities(connection); + if (!CheckError(connection, error_r)) { + for (auto entity : entities) + mpd_entity_free(entity); + return false; + } + + for (auto entity : entities) { + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + Visit(connection, parent, recursive, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error_r); + break; + + case MPD_ENTITY_TYPE_SONG: + Visit(parent, mpd_entity_get_song(entity), visit_song, + error_r); + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + Visit(parent, mpd_entity_get_playlist(entity), + visit_playlist, error_r); + break; + } + + mpd_entity_free(entity); + } + + return CheckError(connection, error_r); +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + GError **error_r) const +{ + // TODO: match + // TODO: auto-reconnect + + struct directory *parent = directory_new(selection.uri, nullptr); + bool success = ::Visit(connection, *parent, selection.recursive, + visit_directory, visit_song, visit_playlist, + error_r); + directory_free(parent); + return success; +} + +const DatabasePlugin proxy_db_plugin = { + "proxy", + ProxyDatabase::Create, +}; diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx new file mode 100644 index 000000000..8e878baca --- /dev/null +++ b/src/db/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif |