Commit ce2b6dc8 authored by Max Kellermann's avatar Max Kellermann

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.
parent 7d16d8c8
......@@ -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 += \
......
......@@ -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
......@@ -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);
};
......
......@@ -98,6 +98,12 @@ Partition::TagModified()
}
void
Partition::TagModified(const char *uri, const Tag &tag) noexcept
{
playlist.TagModified(uri, tag);
}
void
Partition::SyncWithPlayer()
{
playlist.SyncWithPlayer(pc);
......
......@@ -227,6 +227,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.
*/
void SyncWithPlayer();
......
/*
* 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);
}
/*
* 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
/*
* 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
......@@ -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);
......
......@@ -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)
......
......@@ -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
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment