ProxyDatabasePlugin.cxx 26.3 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * 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 "ProxyDatabasePlugin.hxx"
21
#include "db/Interface.hxx"
Max Kellermann's avatar
Max Kellermann committed
22 23 24
#include "db/DatabasePlugin.hxx"
#include "db/DatabaseListener.hxx"
#include "db/Selection.hxx"
25
#include "db/VHelper.hxx"
Max Kellermann's avatar
Max Kellermann committed
26
#include "db/DatabaseError.hxx"
27
#include "db/PlaylistInfo.hxx"
Max Kellermann's avatar
Max Kellermann committed
28
#include "db/LightDirectory.hxx"
29
#include "song/LightSong.hxx"
30
#include "db/Stats.hxx"
31
#include "song/Filter.hxx"
32 33 34
#include "song/UriSongFilter.hxx"
#include "song/BaseSongFilter.hxx"
#include "song/TagSongFilter.hxx"
35
#include "util/Compiler.h"
36
#include "config/Block.hxx"
37
#include "tag/Builder.hxx"
38
#include "tag/Tag.hxx"
Max Kellermann's avatar
Max Kellermann committed
39
#include "tag/ParseName.hxx"
40 41
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
42
#include "util/ScopeExit.hxx"
43
#include "util/RuntimeError.hxx"
44
#include "protocol/Ack.hxx"
45 46 47
#include "event/SocketMonitor.hxx"
#include "event/IdleMonitor.hxx"
#include "Log.hxx"
48 49

#include <mpd/client.h>
50
#include <mpd/async.h>
51 52 53

#include <cassert>
#include <list>
54 55
#include <string>
#include <utility>
56

57
class LibmpdclientError final : public std::runtime_error {
58 59 60 61 62 63
	enum mpd_error code;

public:
	LibmpdclientError(enum mpd_error _code, const char *_msg)
		:std::runtime_error(_msg), code(_code) {}

64
	[[nodiscard]] enum mpd_error GetCode() const {
65 66 67 68
		return code;
	}
};

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
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);
	}
};

88 89 90
class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
	DatabaseListener &listener;

91
	const std::string host;
92
	const std::string password;
93 94
	const unsigned port;
	const bool keepalive;
95 96 97

	struct mpd_connection *connection;

98
	/* this is mutable because GetStats() must be "const" */
99
	mutable std::chrono::system_clock::time_point update_stamp;
100

101 102 103 104 105 106 107 108 109 110 111 112
	/**
	 * 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;

113
public:
114 115
	ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
		      const ConfigBlock &block);
116

117 118 119 120
	static DatabasePtr Create(EventLoop &main_event_loop,
				  EventLoop &io_event_loop,
				  DatabaseListener &listener,
				  const ConfigBlock &block);
121

122
	void Open() override;
123
	void Close() noexcept override;
124
	const LightSong *GetSong(const char *uri_utf8) const override;
125
	void ReturnSong(const LightSong *song) const noexcept override;
126

127 128 129 130
	void Visit(const DatabaseSelection &selection,
		   VisitDirectory visit_directory,
		   VisitSong visit_song,
		   VisitPlaylist visit_playlist) const override;
131

132 133
	RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
						    ConstBuffer<TagType> tag_types) const override;
134

135
	DatabaseStats GetStats(const DatabaseSelection &selection) const override;
136

137
	unsigned Update(const char *uri_utf8, bool discard) override;
138

Max Kellermann's avatar
Max Kellermann committed
139
	std::chrono::system_clock::time_point GetUpdateStamp() const noexcept override {
140
		return update_stamp;
141 142
	}

143
private:
144 145 146
	void Connect();
	void CheckConnection();
	void EnsureConnected();
147

148
	void Disconnect() noexcept;
149 150

	/* virtual methods from SocketMonitor */
151
	bool OnSocketReady(unsigned flags) noexcept override;
152 153

	/* virtual methods from IdleMonitor */
154
	void OnIdle() noexcept override;
155 156
};

157
static constexpr struct {
158
	TagType d;
159 160 161 162 163 164 165 166 167 168
	enum mpd_tag_type s;
} tag_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 },
169 170 171
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ORIGINAL_DATE, MPD_TAG_ORIGINAL_DATE },
#endif
172 173 174 175 176 177 178 179 180
	{ 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 },
181 182 183
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
	{ TAG_MUSICBRAINZ_RELEASETRACKID,
	  MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
184 185 186 187 188 189 190
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,11,0)
	{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
	{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
191
#endif
192 193 194
	{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};

195 196
static void
Copy(TagBuilder &tag, TagType d_tag,
197
     const struct mpd_song *song, enum mpd_tag_type s_tag) noexcept
198 199 200 201 202 203 204 205 206 207 208 209
{

	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)
210
	:LightSong(mpd_song_get_uri(song), tag2)
211
{
212
	const auto _mtime = mpd_song_get_last_modified(song);
213 214
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
215

216 217
	start_time = SongTime::FromS(mpd_song_get_start(song));
	end_time = SongTime::FromS(mpd_song_get_end(song));
218

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
#if LIBMPDCLIENT_CHECK_VERSION(2,15,0)
	const auto *af = mpd_song_get_audio_format(song);
	if (af != nullptr) {
		if (audio_valid_sample_rate(af->sample_rate))
			audio_format.sample_rate = af->sample_rate;


		switch (af->bits) {
		case MPD_SAMPLE_FORMAT_FLOAT:
			audio_format.format = SampleFormat::FLOAT;
			break;

		case MPD_SAMPLE_FORMAT_DSD:
			audio_format.format = SampleFormat::DSD;
			break;

		case 8:
			audio_format.format = SampleFormat::S8;
			break;

		case 16:
			audio_format.format = SampleFormat::S16;
			break;

		case 24:
			audio_format.format = SampleFormat::S24_P32;
			break;

		case 32:
			audio_format.format = SampleFormat::S32;
			break;
		}

		if (audio_valid_channel_count(af->channels))
			audio_format.channels = af->channels;
	}
#endif

257
	TagBuilder tag_builder;
258 259 260 261

	const unsigned duration = mpd_song_get_duration(song);
	if (duration > 0)
		tag_builder.SetDuration(SignedSongTime::FromS(duration));
262 263 264 265 266 267 268

	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);
}

269
gcc_const
270
static enum mpd_tag_type
271
Convert(TagType tag_type) noexcept
272
{
273
	for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
274 275 276 277 278 279
		if (i->d == tag_type)
			return i->s;

	return MPD_TAG_COUNT;
}

280
static void
281
ThrowError(struct mpd_connection *connection)
282
{
283
	const auto code = mpd_connection_get_error(connection);
284

285 286 287 288
	AtScopeExit(connection) {
		mpd_connection_clear_error(connection);
	};

289 290 291 292 293
	if (code == MPD_ERROR_SERVER) {
		/* libmpdclient's "enum mpd_server_error" is the same
		   as our "enum ack" */
		const auto server_error =
			mpd_connection_get_server_error(connection);
294 295
		throw ProtocolError((enum ack)server_error,
				    mpd_connection_get_error_message(connection));
296
	} else {
297 298
		throw LibmpdclientError(code,
					mpd_connection_get_error_message(connection));
299
	}
300
}
301

302 303
static void
CheckError(struct mpd_connection *connection)
304 305
{
	const auto code = mpd_connection_get_error(connection);
306 307
	if (code != MPD_ERROR_SUCCESS)
		ThrowError(connection);
308 309
}

310
static bool
311
SendConstraints(mpd_connection *connection, const ISongFilter &f)
312
{
313 314 315
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
316 317
			return true;

318 319 320 321
		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return mpd_search_add_any_tag_constraint(connection,
								 MPD_OPERATOR_DEFAULT,
								 t->GetValue().c_str());
322

323
		const auto tag = Convert(t->GetTagType());
324 325 326 327 328 329
		if (tag == MPD_TAG_COUNT)
			return true;

		return mpd_search_add_tag_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     tag,
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
						     t->GetValue().c_str());
	} else if (auto u = dynamic_cast<const UriSongFilter *>(&f)) {
		if (u->IsNegated())
			// TODO implement
			return true;

		return mpd_search_add_uri_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     u->GetValue().c_str());
	} else if (auto b = dynamic_cast<const BaseSongFilter *>(&f)) {
		return mpd_search_add_base_constraint(connection,
						      MPD_OPERATOR_DEFAULT,
						      b->GetValue());
	} else
		return true;
345 346 347 348 349
}

static bool
SendConstraints(mpd_connection *connection, const SongFilter &filter)
{
350 351 352 353 354 355 356 357
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0)
		/* with MPD 0.21 (and libmpdclient 2.15), we can pass
		   arbitrary filters as expression */
		return mpd_search_add_expression(connection,
						 filter.ToExpression().c_str());
#endif

358
	for (const auto &i : filter.GetItems())
359
		if (!SendConstraints(connection, *i))
360 361 362 363 364 365 366 367 368
			return false;

	return true;
}

static bool
SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
{
	if (!selection.uri.empty() &&
369 370 371 372
	    !mpd_search_add_base_constraint(connection,
					    MPD_OPERATOR_DEFAULT,
					    selection.uri.c_str()))
		return false;
373 374 375 376 377

	if (selection.filter != nullptr &&
	    !SendConstraints(connection, *selection.filter))
		return false;

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)
	if (selection.sort != TAG_NUM_OF_ITEM_TYPES &&
	    mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0) {
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		if (selection.sort == SORT_TAG_LAST_MODIFIED) {
			if (!mpd_search_add_sort_name(connection, "Last-Modified",
						      selection.descending))
				return false;
		} else {
#endif
			const auto sort = Convert(selection.sort);
			/* if this is an unsupported tag, the sort
			   will be done later by class
			   DatabaseVisitorHelper */
			if (sort != MPD_TAG_COUNT &&
			    !mpd_search_add_sort_tag(connection, sort,
						     selection.descending))
				return false;
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		}
#endif
	}
#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	if (selection.window != RangeArg::All() &&
	    mpd_connection_cmp_server_version(connection, 0, 20, 0) >= 0 &&
	    !mpd_search_add_window(connection, selection.window.start,
				   selection.window.end))
		return false;
#endif

410 411 412
	return true;
}

413
static bool
414
SendGroup(mpd_connection *connection, TagType group)
415
{
416
	assert(group != TAG_NUM_OF_ITEM_TYPES);
417

418 419 420 421
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	const auto tag = Convert(group);
	if (tag == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
422

423
	return mpd_search_add_group_tag(connection, tag);
424 425 426
#else
	(void)connection;

427
	throw std::runtime_error("Grouping requires libmpdclient 2.12");
428 429 430
#endif
}

431 432 433 434 435 436 437 438 439 440 441 442 443
static bool
SendGroup(mpd_connection *connection, ConstBuffer<TagType> group)
{
	while (!group.empty()) {
		if (!SendGroup(connection, group.back()))
		    return false;

		group.pop_back();
	}

	return true;
}

444 445 446 447 448 449
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
			     const ConfigBlock &block)
	:Database(proxy_db_plugin),
	 SocketMonitor(_loop), IdleMonitor(_loop),
	 listener(_listener),
	 host(block.GetBlockValue("host", "")),
450
	 password(block.GetBlockValue("password", "")),
451
	 port(block.GetBlockValue("port", 0U)),
452
	 keepalive(block.GetBlockValue("keepalive", false))
453 454 455
{
}

456
DatabasePtr
457 458
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
		      DatabaseListener &listener,
459
		      const ConfigBlock &block)
460
{
461
	return std::make_unique<ProxyDatabase>(loop, listener, block);
462 463
}

464 465
void
ProxyDatabase::Open()
466
{
467
	update_stamp = std::chrono::system_clock::time_point::min();
468 469 470

	try {
		Connect();
471
	} catch (...) {
472 473
		/* this error is non-fatal, because this plugin will
		   attempt to reconnect again automatically */
474
		LogError(std::current_exception());
475
	}
476 477 478
}

void
479
ProxyDatabase::Close() noexcept
480 481
{
	if (connection != nullptr)
482
		Disconnect();
483 484
}

485 486
void
ProxyDatabase::Connect()
487 488 489
{
	const char *_host = host.empty() ? nullptr : host.c_str();
	connection = mpd_connection_new(_host, port, 0);
490 491
	if (connection == nullptr)
		throw LibmpdclientError(MPD_ERROR_OOM, "Out of memory");
492

493 494
	try {
		CheckError(connection);
495

496 497 498 499
		if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0)
			throw FormatRuntimeError("Connect to MPD %s, but this plugin requires at least version 0.19",
						 mpd_connection_get_server_version(connection));

500 501 502
		if (!password.empty() &&
		    !mpd_run_password(connection, password.c_str()))
			ThrowError(connection);
503
	} catch (...) {
504 505 506
		mpd_connection_free(connection);
		connection = nullptr;

507 508 509 510
		std::throw_with_nested(host.empty()
				       ? std::runtime_error("Failed to connect to remote MPD")
				       : FormatRuntimeError("Failed to connect to remote MPD '%s'",
							    host.c_str()));
511 512
	}

513 514
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	mpd_connection_set_keepalive(connection, keepalive);
515 516 517
#else
	// suppress -Wunused-private-field
	(void)keepalive;
518 519
#endif

520
	idle_received = ~0U;
521 522
	is_idle = false;

523
	SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
524
	IdleMonitor::Schedule();
525 526
}

527 528
void
ProxyDatabase::CheckConnection()
529 530 531
{
	assert(connection != nullptr);

532
	if (!mpd_connection_clear_error(connection)) {
533
		Disconnect();
534 535
		Connect();
		return;
536 537
	}

538 539
	if (is_idle) {
		unsigned idle = mpd_run_noidle(connection);
540 541 542 543 544 545 546
		if (idle == 0) {
			try {
				CheckError(connection);
			} catch (...) {
				Disconnect();
				throw;
			}
547 548 549 550 551 552
		}

		idle_received |= idle;
		is_idle = false;
		IdleMonitor::Schedule();
	}
553 554
}

555 556
void
ProxyDatabase::EnsureConnected()
557
{
558 559 560 561
	if (connection != nullptr)
		CheckConnection();
	else
		Connect();
562 563
}

564
void
565
ProxyDatabase::Disconnect() noexcept
566 567 568
{
	assert(connection != nullptr);

569 570 571
	IdleMonitor::Cancel();
	SocketMonitor::Steal();

572 573 574 575
	mpd_connection_free(connection);
	connection = nullptr;
}

576
bool
577
ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
578 579 580 581 582 583
{
	assert(connection != nullptr);

	if (!is_idle) {
		// TODO: can this happen?
		IdleMonitor::Schedule();
584 585
		SocketMonitor::Cancel();
		return true;
586 587
	}

Max Kellermann's avatar
Max Kellermann committed
588
	auto idle = (unsigned)mpd_recv_idle(connection, false);
589
	if (idle == 0) {
590 591
		try {
			CheckError(connection);
592 593
		} catch (...) {
			LogError(std::current_exception());
594 595 596 597 598 599 600 601 602
			Disconnect();
			return false;
		}
	}

	/* let OnIdle() handle this */
	idle_received |= idle;
	is_idle = false;
	IdleMonitor::Schedule();
603 604
	SocketMonitor::Cancel();
	return true;
605 606 607
}

void
608
ProxyDatabase::OnIdle() noexcept
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
{
	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)) {
626 627
		try {
			ThrowError(connection);
628 629
		} catch (...) {
			LogError(std::current_exception());
630
		}
631 632 633 634 635 636 637 638 639 640 641

		SocketMonitor::Steal();
		mpd_connection_free(connection);
		connection = nullptr;
		return;
	}

	is_idle = true;
	SocketMonitor::ScheduleRead();
}

642
const LightSong *
643
ProxyDatabase::GetSong(const char *uri) const
644
{
645
	// TODO: eliminate the const_cast
646
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
647

648 649
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
650 651

	struct mpd_song *song = mpd_recv_song(connection);
652
	if (!mpd_response_finish(connection)) {
653 654
		if (song != nullptr)
			mpd_song_free(song);
655
		ThrowError(connection);
656 657
	}

658 659 660
	if (song == nullptr)
		throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
				    "No such song");
661

662
	return new AllocatedProxySong(song);
663 664
}

665
void
666
ProxyDatabase::ReturnSong(const LightSong *_song) const noexcept
667
{
668
	assert(_song != nullptr);
669

Max Kellermann's avatar
Max Kellermann committed
670
	auto *song = (AllocatedProxySong *)
671 672
		const_cast<LightSong *>(_song);
	delete song;
673 674
}

675
static void
676
Visit(struct mpd_connection *connection, const char *uri,
677
      bool recursive, const SongFilter *filter,
678 679
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist);
680

681
static void
682
Visit(struct mpd_connection *connection,
683 684
      bool recursive, const SongFilter *filter,
      const struct mpd_directory *directory,
685 686
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
687
{
688
	const char *path = mpd_directory_get_path(directory);
689 690 691 692 693 694

	std::chrono::system_clock::time_point mtime =
		std::chrono::system_clock::time_point::min();
	time_t _mtime = mpd_directory_get_last_modified(directory);
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
695

696 697
	if (visit_directory)
		visit_directory(LightDirectory(path, mtime));
698

699 700
	if (recursive)
		Visit(connection, path, recursive, filter,
701
		      visit_directory, visit_song, visit_playlist);
702 703
}

704 705
gcc_pure
static bool
706
Match(const SongFilter *filter, const LightSong &song) noexcept
707 708 709 710
{
	return filter == nullptr || filter->Match(song);
}

711
static void
712
Visit(const SongFilter *filter,
713
      const mpd_song *_song,
714
      const VisitSong& visit_song)
715 716
{
	if (!visit_song)
717
		return;
718

719
	const ProxySong song(_song);
720 721
	if (Match(filter, song))
		visit_song(song);
722 723
}

724
static void
725
Visit(const struct mpd_playlist *playlist,
726
      const VisitPlaylist& visit_playlist)
727 728
{
	if (!visit_playlist)
729
		return;
730

731 732
	time_t mtime = mpd_playlist_get_last_modified(playlist);

733
	PlaylistInfo p(mpd_playlist_get_path(playlist),
734 735 736
		       mtime > 0
		       ? std::chrono::system_clock::from_time_t(mtime)
		       : std::chrono::system_clock::time_point::min());
737

738
	visit_playlist(p, LightDirectory::Root());
739 740
}

741 742 743 744
class ProxyEntity {
	struct mpd_entity *entity;

public:
745
	explicit ProxyEntity(struct mpd_entity *_entity) noexcept
746 747 748 749
		:entity(_entity) {}

	ProxyEntity(const ProxyEntity &other) = delete;

750
	ProxyEntity(ProxyEntity &&other) noexcept
751 752 753 754
		:entity(other.entity) {
		other.entity = nullptr;
	}

755
	~ProxyEntity() noexcept {
756 757 758 759 760 761
		if (entity != nullptr)
			mpd_entity_free(entity);
	}

	ProxyEntity &operator=(const ProxyEntity &other) = delete;

762
	operator const struct mpd_entity *() const noexcept {
763 764 765 766 767
		return entity;
	}
};

static std::list<ProxyEntity>
768
ReceiveEntities(struct mpd_connection *connection) noexcept
769
{
770
	std::list<ProxyEntity> entities;
771
	struct mpd_entity *entity;
772
	while ((entity = mpd_recv_entity(connection)) != nullptr)
773
		entities.emplace_back(entity);
774 775 776 777 778

	mpd_response_finish(connection);
	return entities;
}

779
static void
780
Visit(struct mpd_connection *connection, const char *uri,
781
      bool recursive, const SongFilter *filter,
782 783
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
784
{
785 786
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
787

788
	std::list<ProxyEntity> entities(ReceiveEntities(connection));
789
	CheckError(connection);
790

791
	for (const auto &entity : entities) {
792 793 794 795 796
		switch (mpd_entity_get_type(entity)) {
		case MPD_ENTITY_TYPE_UNKNOWN:
			break;

		case MPD_ENTITY_TYPE_DIRECTORY:
797 798 799
			Visit(connection, recursive, filter,
			      mpd_entity_get_directory(entity),
			      visit_directory, visit_song, visit_playlist);
800 801 802
			break;

		case MPD_ENTITY_TYPE_SONG:
803
			Visit(filter, mpd_entity_get_song(entity), visit_song);
804 805 806
			break;

		case MPD_ENTITY_TYPE_PLAYLIST:
807 808
			Visit(mpd_entity_get_playlist(entity),
			      visit_playlist);
809 810 811 812 813
			break;
		}
	}
}

814
static void
815 816
SearchSongs(struct mpd_connection *connection,
	    const DatabaseSelection &selection,
817
	    const VisitSong& visit_song)
818
try {
819 820 821 822 823 824 825 826
	assert(selection.recursive);
	assert(visit_song);

	const bool exact = selection.filter == nullptr ||
		!selection.filter->HasFoldCase();

	if (!mpd_search_db_songs(connection, exact) ||
	    !SendConstraints(connection, selection) ||
827 828
	    !mpd_search_commit(connection))
		ThrowError(connection);
829

830
	while (auto *song = mpd_recv_song(connection)) {
831
		AllocatedProxySong song2(song);
832

833 834 835 836 837 838 839 840
		if (Match(selection.filter, song2)) {
			try {
				visit_song(song2);
			} catch (...) {
				mpd_response_finish(connection);
				throw;
			}
		}
841 842
	}

843
	if (!mpd_response_finish(connection))
844
		ThrowError(connection);
845 846 847 848 849
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
850 851
}

852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)

gcc_pure
static bool
IsFilterSupported(const ISongFilter &f) noexcept
{
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
			return false;

		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return true;

		const auto tag = Convert(t->GetTagType());
867
		return tag != MPD_TAG_COUNT;
868 869 870 871 872 873 874 875 876 877 878 879 880 881
	} else if (auto u = dynamic_cast<const UriSongFilter *>(&f)) {
		if (u->IsNegated())
			// TODO implement
			return false;

		return false;
	} else if (dynamic_cast<const BaseSongFilter *>(&f)) {
		return true;
	} else
		return false;
}

gcc_pure
static bool
882 883
IsFilterFullySupported(const SongFilter &filter,
		       const struct mpd_connection *connection) noexcept
884
{
885 886 887 888 889 890 891 892 893
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0)
		/* with MPD 0.21 (and libmpdclient 2.15), we can pass
		   arbitrary filters as expression */
		return true;
#else
	(void)connection;
#endif

894 895 896 897 898 899 900 901 902
	for (const auto &i : filter.GetItems())
		if (!IsFilterSupported(*i))
			return false;

	return true;
}

gcc_pure
static bool
903 904
IsFilterFullySupported(const SongFilter *filter,
		       const struct mpd_connection *connection) noexcept
905 906
{
	return filter == nullptr ||
907
		IsFilterFullySupported(*filter, connection);
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
}

#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)

gcc_pure
static bool
IsSortSupported(TagType tag_type,
		const struct mpd_connection *connection) noexcept
{
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) < 0)
		/* sorting requires MPD 0.21 */
		return false;

	if (tag_type == TagType(SORT_TAG_LAST_MODIFIED)) {
		/* sort "Last-Modified" requires libmpdclient 2.15 for
		   mpd_search_add_sort_name() */
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		return true;
#else
		return false;
#endif
	}

	return Convert(tag_type) != MPD_TAG_COUNT;
}

#endif

gcc_pure
939
static DatabaseSelection
940 941
CheckSelection(DatabaseSelection selection,
	       struct mpd_connection *connection) noexcept
942 943 944
{
	selection.uri.clear();
	selection.filter = nullptr;
945 946 947 948 949 950 951 952 953 954 955

#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)
	if (selection.sort != TAG_NUM_OF_ITEM_TYPES &&
	    IsSortSupported(selection.sort, connection))
		/* we can forward the "sort" parameter to the other
		   MPD */
		selection.sort = TAG_NUM_OF_ITEM_TYPES;
#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	if (selection.window != RangeArg::All() &&
956
	    IsFilterFullySupported(selection.filter, connection))
957 958 959 960 961 962 963
		/* we can forward the "window" parameter to the other
		   MPD */
		selection.window = RangeArg::All();
#else
	(void)connection;
#endif

964 965 966
	return selection;
}

967
void
968 969 970
ProxyDatabase::Visit(const DatabaseSelection &selection,
		     VisitDirectory visit_directory,
		     VisitSong visit_song,
971
		     VisitPlaylist visit_playlist) const
972
{
973
	// TODO: eliminate the const_cast
974
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
975

976 977
	DatabaseVisitorHelper helper(CheckSelection(selection, connection),
				     visit_song);
978

979
	if (!visit_directory && !visit_playlist && selection.recursive &&
980
	    !selection.IsEmpty()) {
981 982
		/* this optimized code path can only be used under
		   certain conditions */
983
		::SearchSongs(connection, selection, visit_song);
984
		helper.Commit();
985
		return;
986
	}
987 988

	/* fall back to recursive walk (slow!) */
989 990 991
	::Visit(connection, selection.uri.c_str(),
		selection.recursive, selection.filter,
		visit_directory, visit_song, visit_playlist);
992 993

	helper.Commit();
994 995
}

996
RecursiveMap<std::string>
997
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
998
				 ConstBuffer<TagType> tag_types) const
999
try {
1000
	// TODO: eliminate the const_cast
1001
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1002

1003
	enum mpd_tag_type tag_type2 = Convert(tag_types.back());
1004 1005
	if (tag_type2 == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
1006

1007 1008 1009
	auto group = tag_types;
	group.pop_back();

1010
	if (!mpd_search_db_tags(connection, tag_type2) ||
1011
	    !SendConstraints(connection, selection) ||
1012
	    !SendGroup(connection, group))
1013
		ThrowError(connection);
1014

1015 1016
	if (!mpd_search_commit(connection))
		ThrowError(connection);
1017

1018 1019 1020
	RecursiveMap<std::string> result;
	std::vector<RecursiveMap<std::string> *> position;
	position.emplace_back(&result);
1021

1022 1023 1024 1025
	while (auto *pair = mpd_recv_pair(connection)) {
		AtScopeExit(this, pair) {
			mpd_return_pair(connection, pair);
		};
1026

1027 1028 1029
		const auto current_type = tag_name_parse_i(pair->name);
		if (current_type == TAG_NUM_OF_ITEM_TYPES)
			continue;
1030

1031 1032 1033 1034
		auto it = std::find(tag_types.begin(), tag_types.end(),
				    current_type);
		if (it == tag_types.end())
			continue;
1035

1036 1037 1038
		size_t i = std::distance(tag_types.begin(), it);
		if (i > position.size())
			continue;
1039

1040 1041
		if (i + 1 < position.size())
			position.resize(i + 1);
1042

1043 1044
		auto &parent = *position[i];
		position.emplace_back(&parent[pair->value]);
1045 1046
	}

1047
	if (!mpd_response_finish(connection))
1048
		ThrowError(connection);
1049 1050

	return result;
1051 1052 1053 1054 1055
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
1056 1057
}

1058 1059
DatabaseStats
ProxyDatabase::GetStats(const DatabaseSelection &selection) const
1060 1061 1062 1063
{
	// TODO: match
	(void)selection;

1064
	// TODO: eliminate the const_cast
1065
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1066

1067 1068
	struct mpd_stats *stats2 =
		mpd_run_stats(connection);
1069 1070
	if (stats2 == nullptr)
		ThrowError(connection);
1071

1072
	update_stamp = std::chrono::system_clock::from_time_t(mpd_stats_get_db_update_time(stats2));
1073

1074
	DatabaseStats stats;
1075
	stats.song_count = mpd_stats_get_number_of_songs(stats2);
1076
	stats.total_duration = std::chrono::seconds(mpd_stats_get_db_play_time(stats2));
1077 1078 1079
	stats.artist_count = mpd_stats_get_number_of_artists(stats2);
	stats.album_count = mpd_stats_get_number_of_albums(stats2);
	mpd_stats_free(stats2);
1080
	return stats;
1081 1082
}

1083
unsigned
1084
ProxyDatabase::Update(const char *uri_utf8, bool discard)
1085
{
1086
	EnsureConnected();
1087 1088 1089 1090 1091

	unsigned id = discard
		? mpd_run_rescan(connection, uri_utf8)
		: mpd_run_update(connection, uri_utf8);
	if (id == 0)
1092
		CheckError(connection);
1093 1094 1095 1096

	return id;
}

1097 1098
const DatabasePlugin proxy_db_plugin = {
	"proxy",
1099
	DatabasePlugin::FLAG_REQUIRE_STORAGE,
1100 1101
	ProxyDatabase::Create,
};