ProxyDatabasePlugin.cxx 27.1 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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
#include "event/SocketEvent.hxx"
46
#include "event/IdleEvent.hxx"
47
#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
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);
	}
86 87 88

	AllocatedProxySong(const AllocatedProxySong &) = delete;
	AllocatedProxySong &operator=(const AllocatedProxySong &) = delete;
89 90
};

91 92
class ProxyDatabase final : public Database {
	SocketEvent socket_event;
93 94
	IdleEvent idle_event;

95 96
	DatabaseListener &listener;

97
	const std::string host;
98
	const std::string password;
99 100
	const unsigned port;
	const bool keepalive;
101 102 103

	struct mpd_connection *connection;

104
	/* this is mutable because GetStats() must be "const" */
105
	mutable std::chrono::system_clock::time_point update_stamp;
106

107 108 109 110 111 112 113 114 115 116 117 118
	/**
	 * 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;

119
public:
120 121
	ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
		      const ConfigBlock &block);
122

123 124 125 126
	static DatabasePtr Create(EventLoop &main_event_loop,
				  EventLoop &io_event_loop,
				  DatabaseListener &listener,
				  const ConfigBlock &block);
127

128
	void Open() override;
129
	void Close() noexcept override;
130
	const LightSong *GetSong(std::string_view uri_utf8) const override;
131
	void ReturnSong(const LightSong *song) const noexcept override;
132

133 134 135 136
	void Visit(const DatabaseSelection &selection,
		   VisitDirectory visit_directory,
		   VisitSong visit_song,
		   VisitPlaylist visit_playlist) const override;
137

138 139
	RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
						    ConstBuffer<TagType> tag_types) const override;
140

141
	DatabaseStats GetStats(const DatabaseSelection &selection) const override;
142

143
	unsigned Update(const char *uri_utf8, bool discard) override;
144

Max Kellermann's avatar
Max Kellermann committed
145
	std::chrono::system_clock::time_point GetUpdateStamp() const noexcept override {
146
		return update_stamp;
147 148
	}

149
private:
150 151 152
	void Connect();
	void CheckConnection();
	void EnsureConnected();
153

154
	void Disconnect() noexcept;
155

156
	void OnSocketReady(unsigned flags) noexcept;
157
	void OnIdle() noexcept;
158 159
};

160
static constexpr struct {
161
	TagType d;
162 163 164 165 166 167 168 169 170 171
	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 },
172 173 174
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ORIGINAL_DATE, MPD_TAG_ORIGINAL_DATE },
#endif
175 176 177 178 179 180 181 182 183
	{ 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 },
184 185
	{ TAG_MUSICBRAINZ_RELEASETRACKID,
	  MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
186 187 188 189
	{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
	{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
190 191 192 193 194 195 196 197 198 199 200 201 202 203
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,17,0)
	{ TAG_WORK, MPD_TAG_WORK },
	{ TAG_CONDUCTOR, MPD_TAG_CONDUCTOR },
	{ TAG_LABEL, MPD_TAG_LABEL },
	{ TAG_GROUPING, MPD_TAG_GROUPING },
	{ TAG_MUSICBRAINZ_WORKID, MPD_TAG_MUSICBRAINZ_WORKID },
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,20,0)
	{ TAG_COMPOSERSORT, MPD_TAG_COMPOSER_SORT },
	{ TAG_ENSEMBLE, MPD_TAG_ENSEMBLE },
	{ TAG_MOVEMENT, MPD_TAG_MOVEMENT },
	{ TAG_MOVEMENTNUMBER, MPD_TAG_MOVEMENTNUMBER },
	{ TAG_LOCATION, MPD_TAG_LOCATION },
204
#endif
205 206 207
	{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};

208 209
static void
Copy(TagBuilder &tag, TagType d_tag,
210
     const struct mpd_song *song, enum mpd_tag_type s_tag) noexcept
211 212 213 214 215 216 217 218 219 220 221 222
{

	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)
223
	:LightSong(mpd_song_get_uri(song), tag2)
224
{
225
	const auto _mtime = mpd_song_get_last_modified(song);
226 227
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
228

229 230
	start_time = SongTime::FromS(mpd_song_get_start(song));
	end_time = SongTime::FromS(mpd_song_get_end(song));
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 257 258 259 260 261 262 263 264 265 266 267 268 269
#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

270
	TagBuilder tag_builder;
271 272 273 274

	const unsigned duration = mpd_song_get_duration(song);
	if (duration > 0)
		tag_builder.SetDuration(SignedSongTime::FromS(duration));
275 276 277 278 279 280 281

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

282
gcc_const
283
static enum mpd_tag_type
284
Convert(TagType tag_type) noexcept
285
{
286
	for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
287 288 289 290 291 292
		if (i->d == tag_type)
			return i->s;

	return MPD_TAG_COUNT;
}

293
[[noreturn]]
294
static void
295
ThrowError(struct mpd_connection *connection)
296
{
297
	const auto code = mpd_connection_get_error(connection);
298

299 300 301 302
	AtScopeExit(connection) {
		mpd_connection_clear_error(connection);
	};

303 304 305 306 307
	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);
308 309
		throw ProtocolError((enum ack)server_error,
				    mpd_connection_get_error_message(connection));
310
	} else {
311 312
		throw LibmpdclientError(code,
					mpd_connection_get_error_message(connection));
313
	}
314
}
315

316 317
static void
CheckError(struct mpd_connection *connection)
318 319
{
	const auto code = mpd_connection_get_error(connection);
320 321
	if (code != MPD_ERROR_SUCCESS)
		ThrowError(connection);
322 323
}

324
static bool
325
SendConstraints(mpd_connection *connection, const ISongFilter &f)
326
{
327 328 329
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
330 331
			return true;

332 333 334 335
		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return mpd_search_add_any_tag_constraint(connection,
								 MPD_OPERATOR_DEFAULT,
								 t->GetValue().c_str());
336

337
		const auto tag = Convert(t->GetTagType());
338 339 340 341 342 343
		if (tag == MPD_TAG_COUNT)
			return true;

		return mpd_search_add_tag_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     tag,
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
						     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;
359 360 361 362 363
}

static bool
SendConstraints(mpd_connection *connection, const SongFilter &filter)
{
364 365 366 367 368 369 370 371
#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

372 373 374
	return std::all_of(
		filter.GetItems().begin(), filter.GetItems().end(),
		[=](const auto &item) { return SendConstraints(connection, *item); });
375 376 377
}

static bool
378 379
SendConstraints(mpd_connection *connection, const DatabaseSelection &selection,
		const RangeArg &window)
380 381
{
	if (!selection.uri.empty() &&
382 383 384 385
	    !mpd_search_add_base_constraint(connection,
					    MPD_OPERATOR_DEFAULT,
					    selection.uri.c_str()))
		return false;
386 387 388 389 390

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

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
	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
	}

413 414
	if (window != RangeArg::All() &&
	    !mpd_search_add_window(connection, window.start, window.end))
415 416
		return false;

417 418 419
	return true;
}

420
static bool
421
SendGroup(mpd_connection *connection, TagType group)
422
{
423
	assert(group != TAG_NUM_OF_ITEM_TYPES);
424

425 426 427 428
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	const auto tag = Convert(group);
	if (tag == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
429

430
	return mpd_search_add_group_tag(connection, tag);
431 432
#else
	(void)connection;
433
	(void)group;
434

435
	throw std::runtime_error("Grouping requires libmpdclient 2.12");
436 437 438
#endif
}

439 440 441 442 443 444 445 446 447 448 449 450 451
static bool
SendGroup(mpd_connection *connection, ConstBuffer<TagType> group)
{
	while (!group.empty()) {
		if (!SendGroup(connection, group.back()))
		    return false;

		group.pop_back();
	}

	return true;
}

452 453 454
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
			     const ConfigBlock &block)
	:Database(proxy_db_plugin),
455
	 socket_event(_loop, BIND_THIS_METHOD(OnSocketReady)),
456
	 idle_event(_loop, BIND_THIS_METHOD(OnIdle)),
457 458
	 listener(_listener),
	 host(block.GetBlockValue("host", "")),
459
	 password(block.GetBlockValue("password", "")),
460
	 port(block.GetBlockValue("port", 0U)),
461
	 keepalive(block.GetBlockValue("keepalive", false))
462 463 464
{
}

465
DatabasePtr
466 467
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
		      DatabaseListener &listener,
468
		      const ConfigBlock &block)
469
{
470
	return std::make_unique<ProxyDatabase>(loop, listener, block);
471 472
}

473 474
void
ProxyDatabase::Open()
475
{
476
	update_stamp = std::chrono::system_clock::time_point::min();
477 478 479

	try {
		Connect();
480
	} catch (...) {
481 482
		/* this error is non-fatal, because this plugin will
		   attempt to reconnect again automatically */
483
		LogError(std::current_exception());
484
	}
485 486 487
}

void
488
ProxyDatabase::Close() noexcept
489 490
{
	if (connection != nullptr)
491
		Disconnect();
492 493
}

494 495
void
ProxyDatabase::Connect()
496 497 498
{
	const char *_host = host.empty() ? nullptr : host.c_str();
	connection = mpd_connection_new(_host, port, 0);
499 500
	if (connection == nullptr)
		throw LibmpdclientError(MPD_ERROR_OOM, "Out of memory");
501

502 503
	try {
		CheckError(connection);
504

505
		if (mpd_connection_cmp_server_version(connection, 0, 20, 0) < 0) {
506 507 508
			const unsigned *version =
				mpd_connection_get_server_version(connection);
			throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
509
						 "plugin requires at least version 0.20",
510 511
						 version[0], version[1], version[2]);
		}
512

513 514 515
		if (!password.empty() &&
		    !mpd_run_password(connection, password.c_str()))
			ThrowError(connection);
516
	} catch (...) {
517 518 519
		mpd_connection_free(connection);
		connection = nullptr;

520 521 522 523
		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()));
524 525
	}

526 527
	mpd_connection_set_keepalive(connection, keepalive);

528
	idle_received = ~0U;
529 530
	is_idle = false;

531
	socket_event.Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
532
	idle_event.Schedule();
533 534
}

535 536
void
ProxyDatabase::CheckConnection()
537 538 539
{
	assert(connection != nullptr);

540
	if (!mpd_connection_clear_error(connection)) {
541
		Disconnect();
542 543
		Connect();
		return;
544 545
	}

546 547
	if (is_idle) {
		unsigned idle = mpd_run_noidle(connection);
548 549 550 551 552 553 554
		if (idle == 0) {
			try {
				CheckError(connection);
			} catch (...) {
				Disconnect();
				throw;
			}
555 556 557 558
		}

		idle_received |= idle;
		is_idle = false;
559
		idle_event.Schedule();
560
	}
561 562
}

563 564
void
ProxyDatabase::EnsureConnected()
565
{
566 567 568 569
	if (connection != nullptr)
		CheckConnection();
	else
		Connect();
570 571
}

572
void
573
ProxyDatabase::Disconnect() noexcept
574 575 576
{
	assert(connection != nullptr);

577
	idle_event.Cancel();
578
	socket_event.ReleaseSocket();
579

580 581 582 583
	mpd_connection_free(connection);
	connection = nullptr;
}

584
void
Rosen Penev's avatar
Rosen Penev committed
585
ProxyDatabase::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
586 587 588 589 590
{
	assert(connection != nullptr);

	if (!is_idle) {
		// TODO: can this happen?
591
		idle_event.Schedule();
592 593
		socket_event.Cancel();
		return;
594 595
	}

Max Kellermann's avatar
Max Kellermann committed
596
	auto idle = (unsigned)mpd_recv_idle(connection, false);
597
	if (idle == 0) {
598 599
		try {
			CheckError(connection);
600 601
		} catch (...) {
			LogError(std::current_exception());
602
			Disconnect();
603
			return;
604 605 606 607 608 609
		}
	}

	/* let OnIdle() handle this */
	idle_received |= idle;
	is_idle = false;
610
	idle_event.Schedule();
611
	socket_event.Cancel();
612 613 614
}

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

639
		socket_event.ReleaseSocket();
640 641 642 643 644 645
		mpd_connection_free(connection);
		connection = nullptr;
		return;
	}

	is_idle = true;
646
	socket_event.ScheduleRead();
647 648
}

649
const LightSong *
650
ProxyDatabase::GetSong(std::string_view uri) const
651
{
652
	// TODO: eliminate the const_cast
653
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
654

655
	if (!mpd_send_list_meta(connection, std::string(uri).c_str()))
656
		ThrowError(connection);
657 658

	struct mpd_song *song = mpd_recv_song(connection);
659
	if (!mpd_response_finish(connection)) {
660 661
		if (song != nullptr)
			mpd_song_free(song);
662
		ThrowError(connection);
663 664
	}

665 666 667
	if (song == nullptr)
		throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
				    "No such song");
668

669
	return new AllocatedProxySong(song);
670 671
}

672
void
673
ProxyDatabase::ReturnSong(const LightSong *_song) const noexcept
674
{
675
	assert(_song != nullptr);
676

Max Kellermann's avatar
Max Kellermann committed
677
	auto *song = (AllocatedProxySong *)
678 679
		const_cast<LightSong *>(_song);
	delete song;
680 681
}

682
static void
683
Visit(struct mpd_connection *connection, const char *uri,
684
      bool recursive, const SongFilter *filter,
685 686
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist);
687

688
static void
689
Visit(struct mpd_connection *connection,
690 691
      bool recursive, const SongFilter *filter,
      const struct mpd_directory *directory,
692 693
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
694
{
695
	const char *path = mpd_directory_get_path(directory);
696 697 698 699 700 701

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

703 704
	if (visit_directory)
		visit_directory(LightDirectory(path, mtime));
705

706 707
	if (recursive)
		Visit(connection, path, recursive, filter,
708
		      visit_directory, visit_song, visit_playlist);
709 710
}

711 712
gcc_pure
static bool
713
Match(const SongFilter *filter, const LightSong &song) noexcept
714 715 716 717
{
	return filter == nullptr || filter->Match(song);
}

718
static void
719
Visit(const SongFilter *filter,
720
      const mpd_song *_song,
721
      const VisitSong& visit_song)
722 723
{
	if (!visit_song)
724
		return;
725

726
	const ProxySong song(_song);
727 728
	if (Match(filter, song))
		visit_song(song);
729 730
}

731
static void
732
Visit(const struct mpd_playlist *playlist,
733
      const VisitPlaylist& visit_playlist)
734 735
{
	if (!visit_playlist)
736
		return;
737

738 739
	time_t mtime = mpd_playlist_get_last_modified(playlist);

740
	PlaylistInfo p(mpd_playlist_get_path(playlist),
741 742 743
		       mtime > 0
		       ? std::chrono::system_clock::from_time_t(mtime)
		       : std::chrono::system_clock::time_point::min());
744

745
	visit_playlist(p, LightDirectory::Root());
746 747
}

748 749 750 751
class ProxyEntity {
	struct mpd_entity *entity;

public:
752
	explicit ProxyEntity(struct mpd_entity *_entity) noexcept
753 754 755 756
		:entity(_entity) {}

	ProxyEntity(const ProxyEntity &other) = delete;

757
	ProxyEntity(ProxyEntity &&other) noexcept
758 759 760 761
		:entity(other.entity) {
		other.entity = nullptr;
	}

762
	~ProxyEntity() noexcept {
763 764 765 766 767 768
		if (entity != nullptr)
			mpd_entity_free(entity);
	}

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

769
	operator const struct mpd_entity *() const noexcept {
770 771 772 773 774
		return entity;
	}
};

static std::list<ProxyEntity>
775
ReceiveEntities(struct mpd_connection *connection) noexcept
776
{
777
	std::list<ProxyEntity> entities;
778
	struct mpd_entity *entity;
779
	while ((entity = mpd_recv_entity(connection)) != nullptr)
780
		entities.emplace_back(entity);
781 782 783 784 785

	mpd_response_finish(connection);
	return entities;
}

786
static void
787
Visit(struct mpd_connection *connection, const char *uri,
788
      bool recursive, const SongFilter *filter,
789 790
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
791
{
792 793
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
794

795
	std::list<ProxyEntity> entities(ReceiveEntities(connection));
796
	CheckError(connection);
797

798
	for (const auto &entity : entities) {
799 800 801 802 803
		switch (mpd_entity_get_type(entity)) {
		case MPD_ENTITY_TYPE_UNKNOWN:
			break;

		case MPD_ENTITY_TYPE_DIRECTORY:
804 805 806
			Visit(connection, recursive, filter,
			      mpd_entity_get_directory(entity),
			      visit_directory, visit_song, visit_playlist);
807 808 809
			break;

		case MPD_ENTITY_TYPE_SONG:
810
			Visit(filter, mpd_entity_get_song(entity), visit_song);
811 812 813
			break;

		case MPD_ENTITY_TYPE_PLAYLIST:
814 815
			Visit(mpd_entity_get_playlist(entity),
			      visit_playlist);
816 817 818 819 820
			break;
		}
	}
}

821
static void
822 823
SearchSongs(struct mpd_connection *connection,
	    const DatabaseSelection &selection,
824
	    const VisitSong& visit_song)
825
try {
826 827 828 829 830 831
	assert(selection.recursive);
	assert(visit_song);

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

832 833 834
	/* request only this number of songs at a time to avoid
	   blowing the server's max_output_buffer_size limit */
	constexpr unsigned LIMIT = 4096;
835

836
	auto remaining_window = selection.window;
837

838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
	while (remaining_window.start < remaining_window.end) {
		auto window = remaining_window;
		if (window.end - window.start > LIMIT)
			window.end = window.start + LIMIT;

		if (!mpd_search_db_songs(connection, exact) ||
		    !SendConstraints(connection, selection, window) ||
		    !mpd_search_commit(connection))
			ThrowError(connection);

		while (auto *song = mpd_recv_song(connection)) {
			++window.start;

			AllocatedProxySong song2(song);

			if (Match(selection.filter, song2)) {
				try {
					visit_song(song2);
				} catch (...) {
					mpd_response_finish(connection);
					throw;
				}
860 861
			}
		}
862

863 864 865 866 867 868 869 870 871 872 873
		if (!mpd_response_finish(connection))
			ThrowError(connection);

		if (window.start != window.end)
			/* the other MPD has given us less than we
			   requested - this means there's no more
			   data */
			break;

		remaining_window.start = window.end;
	}
874 875 876 877 878
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
879 880
}

881 882 883 884 885 886 887 888 889 890 891 892 893
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());
894
		return tag != MPD_TAG_COUNT;
895 896 897 898 899 900 901 902 903 904 905 906 907 908
	} 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
909 910
IsFilterFullySupported(const SongFilter &filter,
		       const struct mpd_connection *connection) noexcept
911
{
912 913 914 915 916 917 918 919 920
#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

921 922
	return std::all_of(filter.GetItems().begin(), filter.GetItems().end(),
			   [](const auto &item) { return IsFilterSupported(*item); });
923 924 925 926
}

gcc_pure
static bool
927 928
IsFilterFullySupported(const SongFilter *filter,
		       const struct mpd_connection *connection) noexcept
929 930
{
	return filter == nullptr ||
931
		IsFilterFullySupported(*filter, connection);
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956
}

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

gcc_pure
957
static DatabaseSelection
958 959
CheckSelection(DatabaseSelection selection,
	       struct mpd_connection *connection) noexcept
960 961 962
{
	selection.uri.clear();
	selection.filter = nullptr;
963 964 965 966 967 968 969 970

	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;

	if (selection.window != RangeArg::All() &&
971
	    IsFilterFullySupported(selection.filter, connection))
972 973 974 975
		/* we can forward the "window" parameter to the other
		   MPD */
		selection.window = RangeArg::All();

976 977 978
	return selection;
}

979
void
980 981 982
ProxyDatabase::Visit(const DatabaseSelection &selection,
		     VisitDirectory visit_directory,
		     VisitSong visit_song,
983
		     VisitPlaylist visit_playlist) const
984
{
985
	// TODO: eliminate the const_cast
986
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
987

988 989
	DatabaseVisitorHelper helper(CheckSelection(selection, connection),
				     visit_song);
990

991
	if (!visit_directory && !visit_playlist && selection.recursive &&
992
	    !selection.IsEmpty()) {
993 994
		/* this optimized code path can only be used under
		   certain conditions */
995
		::SearchSongs(connection, selection, visit_song);
996
		helper.Commit();
997
		return;
998
	}
999 1000

	/* fall back to recursive walk (slow!) */
1001 1002 1003
	::Visit(connection, selection.uri.c_str(),
		selection.recursive, selection.filter,
		visit_directory, visit_song, visit_playlist);
1004 1005

	helper.Commit();
1006 1007
}

1008
RecursiveMap<std::string>
1009
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
1010
				 ConstBuffer<TagType> tag_types) const
1011
try {
1012
	// TODO: eliminate the const_cast
1013
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1014

1015
	enum mpd_tag_type tag_type2 = Convert(tag_types.back());
1016 1017
	if (tag_type2 == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
1018

1019 1020 1021
	auto group = tag_types;
	group.pop_back();

1022
	if (!mpd_search_db_tags(connection, tag_type2) ||
1023
	    !SendConstraints(connection, selection, selection.window) ||
1024
	    !SendGroup(connection, group))
1025
		ThrowError(connection);
1026

1027 1028
	if (!mpd_search_commit(connection))
		ThrowError(connection);
1029

1030 1031 1032
	RecursiveMap<std::string> result;
	std::vector<RecursiveMap<std::string> *> position;
	position.emplace_back(&result);
1033

1034 1035 1036 1037
	while (auto *pair = mpd_recv_pair(connection)) {
		AtScopeExit(this, pair) {
			mpd_return_pair(connection, pair);
		};
1038

1039 1040 1041
		const auto current_type = tag_name_parse_i(pair->name);
		if (current_type == TAG_NUM_OF_ITEM_TYPES)
			continue;
1042

1043 1044 1045 1046
		auto it = std::find(tag_types.begin(), tag_types.end(),
				    current_type);
		if (it == tag_types.end())
			continue;
1047

1048 1049 1050
		size_t i = std::distance(tag_types.begin(), it);
		if (i > position.size())
			continue;
1051

1052 1053
		if (i + 1 < position.size())
			position.resize(i + 1);
1054

1055 1056
		auto &parent = *position[i];
		position.emplace_back(&parent[pair->value]);
1057 1058
	}

1059
	if (!mpd_response_finish(connection))
1060
		ThrowError(connection);
1061 1062

	return result;
1063 1064 1065 1066 1067
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
1068 1069
}

1070 1071
DatabaseStats
ProxyDatabase::GetStats(const DatabaseSelection &selection) const
1072 1073 1074 1075
{
	// TODO: match
	(void)selection;

1076
	// TODO: eliminate the const_cast
1077
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1078

1079 1080
	struct mpd_stats *stats2 =
		mpd_run_stats(connection);
1081 1082
	if (stats2 == nullptr)
		ThrowError(connection);
1083

1084
	update_stamp = std::chrono::system_clock::from_time_t(mpd_stats_get_db_update_time(stats2));
1085

1086
	DatabaseStats stats;
1087
	stats.song_count = mpd_stats_get_number_of_songs(stats2);
1088
	stats.total_duration = std::chrono::seconds(mpd_stats_get_db_play_time(stats2));
1089 1090 1091
	stats.artist_count = mpd_stats_get_number_of_artists(stats2);
	stats.album_count = mpd_stats_get_number_of_albums(stats2);
	mpd_stats_free(stats2);
1092
	return stats;
1093 1094
}

1095
unsigned
1096
ProxyDatabase::Update(const char *uri_utf8, bool discard)
1097
{
1098
	EnsureConnected();
1099 1100 1101 1102 1103

	unsigned id = discard
		? mpd_run_rescan(connection, uri_utf8)
		: mpd_run_update(connection, uri_utf8);
	if (id == 0)
1104
		CheckError(connection);
1105 1106 1107 1108

	return id;
}

1109 1110
const DatabasePlugin proxy_db_plugin = {
	"proxy",
1111
	DatabasePlugin::FLAG_REQUIRE_STORAGE,
1112 1113
	ProxyDatabase::Create,
};