RemoteTagCache: new glue class for integrating RemoteTagScanner

This commit also puts an instance of RemoteTagScanner into the
Instance class, and hooks it into the "add" and "addid" commands.
This commit is contained in:
Max Kellermann 2018-01-29 12:02:14 +01:00
parent 7d16d8c887
commit ce2b6dc84d
11 changed files with 417 additions and 0 deletions

View File

@ -183,6 +183,12 @@ libmpd_a_SOURCES = \
src/SongFilter.cxx src/SongFilter.hxx \
src/PlaylistFile.cxx src/PlaylistFile.hxx
if ENABLE_CURL
libmpd_a_SOURCES += \
src/RemoteTagCache.cxx src/RemoteTagCache.hxx \
src/RemoteTagCacheHandler.hxx
endif
if ANDROID
else
libmpd_a_SOURCES += \

View File

@ -23,6 +23,11 @@
#include "Idle.hxx"
#include "Stats.hxx"
#ifdef ENABLE_CURL
#include "RemoteTagCache.hxx"
#include "util/UriUtil.hxx"
#endif
#ifdef ENABLE_DATABASE
#include "db/DatabaseError.hxx"
@ -114,3 +119,31 @@ Instance::LostNeighbor(gcc_unused const NeighborInfo &info) noexcept
}
#endif
#ifdef ENABLE_CURL
void
Instance::LookupRemoteTag(const char *uri) noexcept
{
if (!uri_has_scheme(uri))
return;
if (!remote_tag_cache)
remote_tag_cache = std::make_unique<RemoteTagCache>(event_loop,
*this);
remote_tag_cache->Lookup(uri);
}
void
Instance::OnRemoteTag(const char *uri, const Tag &tag) noexcept
{
if (!tag.IsDefined())
/* boring */
return;
for (auto &partition : partitions)
partition.TagModified(uri, tag);
}
#endif

View File

@ -26,6 +26,10 @@
#include "event/MaskMonitor.hxx"
#include "Compiler.h"
#ifdef ENABLE_CURL
#include "RemoteTagCacheHandler.hxx"
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS
#include "neighbor/Listener.hxx"
class NeighborGlue;
@ -38,11 +42,13 @@ class Storage;
class UpdateService;
#endif
#include <memory>
#include <list>
class ClientList;
struct Partition;
class StateFile;
class RemoteTagCache;
/**
* A utility class which, when used as the first base class, ensures
@ -66,6 +72,9 @@ struct Instance final
#ifdef ENABLE_NEIGHBOR_PLUGINS
public NeighborListener
#endif
#ifdef ENABLE_CURL
, public RemoteTagCacheHandler
#endif
{
EventThread io_thread;
@ -87,6 +96,10 @@ struct Instance final
UpdateService *update = nullptr;
#endif
#ifdef ENABLE_CURL
std::unique_ptr<RemoteTagCache> remote_tag_cache;
#endif
ClientList *client_list;
std::list<Partition> partitions;
@ -139,6 +152,14 @@ struct Instance final
void FinishShutdownUpdate() noexcept;
void ShutdownDatabase() noexcept;
#ifdef ENABLE_CURL
void LookupRemoteTag(const char *uri) noexcept;
#else
void LookupRemoteTag(const char *) noexcept {
/* no-op */
}
#endif
private:
#ifdef ENABLE_DATABASE
void OnDatabaseModified() override;
@ -151,6 +172,11 @@ private:
void LostNeighbor(const NeighborInfo &info) noexcept override;
#endif
#ifdef ENABLE_CURL
/* virtual methods from class RemoteTagCacheHandler */
void OnRemoteTag(const char *uri, const Tag &tag) noexcept override;
#endif
/* callback for #idle_monitor */
void OnIdle(unsigned mask);
};

View File

@ -97,6 +97,12 @@ Partition::TagModified()
playlist.TagModified(std::move(*song));
}
void
Partition::TagModified(const char *uri, const Tag &tag) noexcept
{
playlist.TagModified(uri, tag);
}
void
Partition::SyncWithPlayer()
{

View File

@ -226,6 +226,12 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
*/
void TagModified();
/**
* The tag of the given song has been modified. Propagate the
* change to all instances of this song in the queue.
*/
void TagModified(const char *uri, const Tag &tag) noexcept;
/**
* Synchronize the player with the play queue.
*/

140
src/RemoteTagCache.cxx Normal file
View File

@ -0,0 +1,140 @@
/*
* Copyright 2003-2018 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 "RemoteTagCache.hxx"
#include "RemoteTagCacheHandler.hxx"
#include "input/ScanTags.hxx"
#include "util/DeleteDisposer.hxx"
#include "Log.hxx"
RemoteTagCache::RemoteTagCache(EventLoop &event_loop,
RemoteTagCacheHandler &_handler) noexcept
:handler(_handler),
defer_invoke_handler(event_loop, BIND_THIS_METHOD(InvokeHandlers)),
map(typename KeyMap::bucket_traits(&buckets.front(), buckets.size()))
{
}
RemoteTagCache::~RemoteTagCache() noexcept
{
map.clear_and_dispose(DeleteDisposer());
}
void
RemoteTagCache::Lookup(const std::string &uri) noexcept
{
std::unique_lock<Mutex> lock(mutex);
KeyMap::insert_commit_data hint;
auto result = map.insert_check(uri, Item::Hash(), Item::Equal(), hint);
if (result.second) {
auto *item = new Item(*this, uri);
map.insert_commit(*item, hint);
waiting_list.push_back(*item);
lock.unlock();
try {
item->scanner = InputScanTags(uri.c_str(), *item);
if (!item->scanner) {
/* unsupported */
lock.lock();
ItemResolved(*item);
return;
}
item->scanner->Start();
} catch (...) {
FormatError(std::current_exception(),
"Failed to scan tags of '%s'",
uri.c_str());
item->scanner.reset();
lock.lock();
ItemResolved(*item);
return;
}
} else if (result.first->scanner) {
/* already scanning this one - no-op */
} else {
/* already finished: re-invoke the handler */
auto &item = *result.first;
idle_list.erase(waiting_list.iterator_to(item));
invoke_list.push_back(item);
ScheduleInvokeHandlers();
}
}
void
RemoteTagCache::ItemResolved(Item &item) noexcept
{
waiting_list.erase(waiting_list.iterator_to(item));
invoke_list.push_back(item);
ScheduleInvokeHandlers();
}
void
RemoteTagCache::InvokeHandlers() noexcept
{
const std::lock_guard<Mutex> lock(mutex);
while (!invoke_list.empty()) {
auto &item = invoke_list.front();
invoke_list.pop_front();
idle_list.push_back(item);
const ScopeUnlock unlock(mutex);
handler.OnRemoteTag(item.uri.c_str(), item.tag);
}
/* evict items if there are too many */
while (map.size() > MAX_SIZE && !idle_list.empty()) {
auto *item = &idle_list.front();
idle_list.pop_front();
map.erase(map.iterator_to(*item));
delete item;
}
}
void
RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
{
tag = std::move(_tag);
scanner.reset();
const std::lock_guard<Mutex> lock(parent.mutex);
parent.ItemResolved(*this);
}
void
RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
{
FormatError(e, "Failed to scan tags of '%s'", uri.c_str());
scanner.reset();
const std::lock_guard<Mutex> lock(parent.mutex);
parent.ItemResolved(*this);
}

144
src/RemoteTagCache.hxx Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright 2003-2018 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_REMOTE_TAG_CACHE_HXX
#define MPD_REMOTE_TAG_CACHE_HXX
#include "check.h"
#include "input/RemoteTagScanner.hxx"
#include "tag/Tag.hxx"
#include "event/DeferEvent.hxx"
#include "thread/Mutex.hxx"
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <string>
class RemoteTagCacheHandler;
/**
* A cache for tags received via #RemoteTagScanner.
*/
class RemoteTagCache final {
static constexpr size_t MAX_SIZE = 4096;
RemoteTagCacheHandler &handler;
DeferEvent defer_invoke_handler;
Mutex mutex;
struct Item final
: public boost::intrusive::unordered_set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
RemoteTagHandler
{
RemoteTagCache &parent;
const std::string uri;
std::unique_ptr<RemoteTagScanner> scanner;
Tag tag;
template<typename U>
Item(RemoteTagCache &_parent, U &&_uri) noexcept
:parent(_parent), uri(std::forward<U>(_uri)) {}
/* virtual methods from RemoteTagHandler */
void OnRemoteTag(Tag &&tag) noexcept override;
void OnRemoteTagError(std::exception_ptr e) noexcept override;
struct Hash : std::hash<std::string> {
using std::hash<std::string>::operator();
gcc_pure
std::size_t operator()(const Item &item) const noexcept {
return std::hash<std::string>::operator()(item.uri);
}
};
struct Equal {
gcc_pure
bool operator()(const Item &a,
const Item &b) const noexcept {
return a.uri == b.uri;
}
gcc_pure
bool operator()(const std::string &a,
const Item &b) const noexcept {
return a == b.uri;
}
};
};
typedef boost::intrusive::list<Item,
boost::intrusive::constant_time_size<false>> ItemList;
/**
* These items have been resolved completely (successful or
* failed). All callbacks have been invoked. The oldest
* comes first in the list, and is the first one to be evicted
* if the cache is full.
*/
ItemList idle_list;
/**
* A #RemoteTagScanner instances is currently busy on fetching
* information, and we're waiting for our #RemoteTagHandler
* methods to be invoked.
*/
ItemList waiting_list;
/**
* These items have just been resolved, and the
* #RemoteTagCacheHandler is about to be invoked. After that,
* they will be moved to the #idle_list.
*/
ItemList invoke_list;
typedef boost::intrusive::unordered_set<Item,
boost::intrusive::hash<Item::Hash>,
boost::intrusive::equal<Item::Equal>,
boost::intrusive::constant_time_size<true>> KeyMap;
std::array<typename KeyMap::bucket_type, 127> buckets;
KeyMap map;
public:
RemoteTagCache(EventLoop &event_loop,
RemoteTagCacheHandler &_handler) noexcept;
~RemoteTagCache() noexcept;
void Lookup(const std::string &uri) noexcept;
private:
void InvokeHandlers() noexcept;
void ScheduleInvokeHandlers() noexcept {
defer_invoke_handler.Schedule();
}
void ItemResolved(Item &item) noexcept;
};
#endif

View File

@ -0,0 +1,30 @@
/*
* Copyright 2003-2018 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_REMOTE_TAG_CACHE_HANDLER_HXX
#define MPD_REMOTE_TAG_CACHE_HANDLER_HXX
struct Tag;
class RemoteTagCacheHandler {
public:
virtual void OnRemoteTag(const char *uri, const Tag &tag) noexcept = 0;
};
#endif

View File

@ -32,6 +32,7 @@
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "BulkEdit.hxx"
#include "util/ConstBuffer.hxx"
#include "util/StringAPI.hxx"
@ -87,6 +88,10 @@ handle_add(Client &client, Request args, Response &r)
);
switch (located_uri.type) {
case LocatedUri::Type::ABSOLUTE:
AddUri(client, located_uri);
client.GetInstance().LookupRemoteTag(located_uri.canonical_uri);
return CommandResult::OK;
case LocatedUri::Type::PATH:
AddUri(client, located_uri);
return CommandResult::OK;
@ -107,6 +112,7 @@ handle_addid(Client &client, Request args, Response &r)
auto &partition = client.GetPartition();
const SongLoader loader(client);
unsigned added_id = partition.AppendURI(loader, uri);
partition.instance.LookupRemoteTag(uri);
if (args.size == 2) {
unsigned to = args.ParseUnsigned(1);

View File

@ -43,6 +43,24 @@ playlist::TagModified(DetachedSong &&song)
OnModified();
}
void
playlist::TagModified(const char *uri, const Tag &tag) noexcept
{
bool modified = false;
for (unsigned i = 0; i < queue.length; ++i) {
auto &song = *queue.items[i].song;
if (song.IsURI(uri)) {
song.SetTag(tag);
queue.ModifyAtPosition(i);
modified = true;
}
}
if (modified)
OnModified();
}
inline void
playlist::QueueSongOrder(PlayerControl &pc, unsigned order)

View File

@ -23,6 +23,7 @@
#include "queue/Queue.hxx"
enum TagType : uint8_t;
struct Tag;
struct PlayerControl;
class DetachedSong;
class Database;
@ -192,6 +193,7 @@ public:
* the song matches.
*/
void TagModified(DetachedSong &&song);
void TagModified(const char *uri, const Tag &tag) noexcept;
#ifdef ENABLE_DATABASE
/**