diff options
Diffstat (limited to 'src/db/ProxyDatabasePlugin.cxx')
-rw-r--r-- | src/db/ProxyDatabasePlugin.cxx | 320 |
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 ¶m, + ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener) + :SocketMonitor(_loop), IdleMonitor(_loop), + listener(_listener) {} + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, 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 ¶m, Error &error) +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; @@ -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); |