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:
parent
7d16d8c887
commit
ce2b6dc84d
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue